//! Shared types for order book implementations.

use crate::{
    book::common::events::CancelCause,
    book::protocol::{
        command::{Persistence, Side},
        reject::RejectReason,
    },
    types::*,
};
use std::collections::VecDeque;

// ============================================================================
// Market State
// ============================================================================

/// Book-local market states (single market per book).
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    Hash,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
    strum::Display,
    strum::AsRefStr,
)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
#[strum(serialize_all = "SCREAMING_SNAKE_CASE")]
pub enum BookMarketState {
    /// Trading is open.
    Open,
    /// Temporarily halted (not matchable)
    Suspended,
    /// Administratively halted (not matchable; cancellations allowed)
    Halted,
    /// Event finished, awaiting settlement (not matchable)
    Closed,
    /// Market voided, all bets cancelled (terminal)
    Voided,
    /// Market settled with results (terminal)
    Settled,
}

impl BookMarketState {
    /// Whether matching is allowed in this state.
    pub fn is_matchable(self) -> bool {
        matches!(self, Self::Open)
    }

    /// Whether the market is terminal (reject all commands that mutate trading state).
    pub fn is_terminal(self) -> bool {
        matches!(self, Self::Closed | Self::Voided | Self::Settled)
    }

    /// Whether the market is administratively halted.
    pub fn is_halted(self) -> bool {
        matches!(self, Self::Halted)
    }
}

/// Returns `Err(NoChange)` when the market is already in the requested phase.
pub fn ensure_phase_change(
    current: MarketPhase,
    to: MarketPhase,
) -> Result<(), crate::book::protocol::reject::RejectReason> {
    if current == to {
        return Err(crate::book::protocol::reject::RejectReason::NoChange);
    }
    Ok(())
}

/// Rejects order-entry commands when the market is not currently matchable.
pub fn ensure_can_accept_new_orders(state: BookMarketState) -> Result<(), RejectReason> {
    if state.is_matchable() {
        Ok(())
    } else {
        Err(RejectReason::MarketNotOpen)
    }
}

/// Returns `Err(NoChange)` when the book is already in the requested state.
pub fn ensure_state_change(
    current: BookMarketState,
    to: BookMarketState,
) -> Result<(), crate::book::protocol::reject::RejectReason> {
    if current == to {
        return Err(crate::book::protocol::reject::RejectReason::NoChange);
    }
    Ok(())
}

// ============================================================================
// Close Process
// ============================================================================

/// State tracking for the batched market close process.
///
/// When a market closes with many resting orders, cancellations are split
/// into batches to avoid unbounded event emission. This struct tracks
/// progress through the close process.
///
/// ## Cursor Semantics
///
/// The `cursor_after` field implements pagination over the order set:
///
/// - Orders are processed in ascending `OrderId` order (BTreeMap iteration)
/// - After each batch, `cursor_after` is set to the last processed `OrderId`
/// - The next batch processes orders where `order_id > cursor_after`
/// - `None` means "start from beginning" (first batch)
///
/// This cursor-based approach ensures **idempotent recovery**: if events
/// are replayed during recovery, the cursor prevents re-cancelling orders
/// that were already processed.
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct CloseProcessState {
    /// Maximum events per batch (controls batch size).
    ///
    /// Set from the `CloseMarket` command's `batch_max_events` parameter.
    /// Minimum is 2 (to fit state-change event).
    pub batch_max_events: u16,

    /// Pagination cursor: last processed OrderId, or None if starting.
    ///
    /// The next batch will process orders with `order_id > cursor_after`.
    /// This enables resumption after crash/restart without double-cancellation.
    pub cursor_after: Option<OrderId>,

    /// Total live orders when close started (for progress tracking).
    ///
    /// This is the count of ExecutableUnmatched + ExecutablePartiallyMatched
    /// orders at the moment the close process began.
    pub total_live_orders: u64,

    /// Running count of orders cancelled so far.
    ///
    /// Updated after each batch. When `cancelled_total >= total_live_orders`,
    /// the close process is complete.
    pub cancelled_total: u64,

    /// Number of batches completed (for monitoring/debugging).
    pub chunks_done: u32,
}

/// State tracking for an in-progress batch-cancel command.
#[derive(
    Debug,
    Clone,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct CancelProcessState {
    /// Maximum number of order cancellations emitted per batch event.
    pub batch_max_events: u16,
    /// Pagination cursor: last processed OrderId.
    pub cursor_after: Option<OrderId>,
    /// Command start timestamp in unix millis (freeze horizon for deterministic filtering).
    pub started_at_ms: i64,
    /// Inclusive created-at lower bound.
    pub from_created_at_inclusive_ms: Option<i64>,
    /// Inclusive created-at upper bound.
    pub to_created_at_inclusive_ms: Option<i64>,
    /// Optional account filter.
    pub account_filter: Option<AccountId>,
    /// Optional runner filter.
    pub runner_filter: Option<RunnerId>,
    /// Batch reason propagated to emitted events.
    pub reason: String,
    /// Metadata JSON that must be attached only to the final batch event.
    pub final_event_metadata_json: Option<String>,
    /// Running count of cancelled orders.
    pub cancelled_total: u64,
    /// Number of emitted chunks.
    pub chunks_done: u32,
}

/// State tracking for generic batched order-state mutation processes.
#[derive(
    Debug,
    Clone,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct OrderBatchProcessState {
    /// Maximum number of order ids emitted in each batch event.
    pub batch_max_events: u16,
    /// Pagination cursor: last processed OrderId.
    pub cursor_after: Option<OrderId>,
    /// Cancellation cause propagated into emitted events.
    pub cancel_cause: CancelCause,
    /// Optional detail propagated into emitted events.
    pub cause_detail: Option<String>,
    /// Running count of processed orders.
    pub processed_total: u64,
    /// Number of emitted chunks.
    pub chunks_done: u32,
}

/// Unified process slot for batched operations. At most one can be active per market.
#[derive(
    Debug,
    Clone,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub enum BatchProcessState {
    Close(CloseProcessState),
    Cancel(CancelProcessState),
    Lapse(OrderBatchProcessState),
    Void(OrderBatchProcessState),
}

// ============================================================================
// Order Types
// ============================================================================

/// Order lifecycle as owned by the book.
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    Hash,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
    strum::Display,
    strum::AsRefStr,
)]
pub enum BookOrderState {
    ExecutableUnmatched,
    ExecutablePartiallyMatched,
    ExecutionComplete,
    Cancelled,
    Lapsed,
    Voided,
}

// ============================================================================
// Settlement Types
// ============================================================================

/// Result for a runner at settlement.
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    Hash,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub enum RunnerResult {
    Winner,
    Loser,
    Removed,
    RemovedVacant,
}

/// Signed money for P&L calculations.
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct SignedMoney(pub i64);

impl SignedMoney {
    pub fn zero() -> Self {
        Self(0)
    }
}

// ============================================================================
// Book Order
// ============================================================================

/// Canonical order metadata shared across books.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BookOrderInfo {
    pub order_id: OrderId,
    pub account_id: AccountId,
    pub correlation_id: Option<CorrelationId>,
    pub side: Side,
    pub state: BookOrderState,
    pub created_at: DateTime,
    pub last_updated_at: DateTime,
}

/// Canonical order representation stored by the book.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BookOrder {
    pub info: BookOrderInfo,
    pub runner_id: RunnerId,
    pub price: OddsX10000,
    pub stake: Money,
    pub matched: Money,
    pub persistence: Persistence,
}

impl BookOrder {
    /// Remaining unmatched stake.
    pub fn remaining(&self) -> Money {
        Money(self.stake.0.saturating_sub(self.matched.0).max(0))
    }
}

// ============================================================================
// Market Depth
// ============================================================================

/// Price and size at a single level.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct PriceSize {
    pub price: OddsX10000,
    pub size: Money,
}

/// Available prices for a runner.
#[derive(Debug, Clone, Default)]
pub struct RunnerPrices {
    pub runner_id: RunnerId,
    pub available_to_back: Vec<PriceSize>,
    pub available_to_lay: Vec<PriceSize>,
}

/// Price and size for prediction markets (canonical YES-only).
#[derive(
    Debug,
    Clone,
    Copy,
    PartialEq,
    Eq,
    serde::Serialize,
    serde::Deserialize,
    rkyv::Archive,
    rkyv::Serialize,
    rkyv::Deserialize,
)]
pub struct BinaryPriceSize {
    pub price_ticks: u16,
    pub size_shares: u64,
}

/// Depth snapshot for a canonical YES-only prediction market.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BinaryDepth {
    pub max_price_ticks: u16,
    /// Bid levels (highest `price_ticks` first).
    pub bids: Vec<BinaryPriceSize>,
    /// Ask levels (lowest `price_ticks` first).
    pub asks: Vec<BinaryPriceSize>,
}

// ============================================================================
// Price Level (internal)
// ============================================================================

/// Internal per-price FIFO queue.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct PriceLevel {
    pub fifo: VecDeque<OrderId>,
    pub total_remaining: Money,
}

impl PriceLevel {
    pub fn new() -> Self {
        Self {
            fifo: VecDeque::new(),
            total_remaining: Money::zero(),
        }
    }
}

impl Default for PriceLevel {
    fn default() -> Self {
        Self::new()
    }
}
