use crate::{
    OrderId,
    domain::{
        event::{
            CancellationReason,
            Identity,
        },
        trade::TradeId,
    },
    fuel_types::{
        AssetId,
        Bytes32,
        ContractId,
        TxId,
    },
    parse::{
        HexDisplayFromStr,
        serialize_hex,
    },
    primitives::{
        OrderType,
        Side,
    },
};
use serde_with::{
    DisplayFromStr,
    serde_as,
};
use sha2::{
    Digest,
    Sha256,
};
use std::collections::BTreeMap;

pub type Price = u64;
pub type Quantity = u64;
pub type MarketId = Bytes32;

#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum BaseQuantity {
    Quantity(Quantity),
    Infinite,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum UnderlyingAsset {
    Base {
        remaining_quantity: Quantity,
    },
    Quote {
        desired_base_quantity: BaseQuantity,
        remaining_quote_token: Quantity,
    },
}

#[derive(
    Copy, Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash,
)]
pub struct MarketIdAssets {
    pub base_asset: AssetId,
    pub quote_asset: AssetId,
}

impl MarketIdAssets {
    pub fn market_id(&self) -> MarketId {
        let bytes: Vec<u8> = self
            .quote_asset
            .into_iter()
            .chain(self.base_asset.to_vec())
            .collect();
        let digest = Sha256::digest(bytes.as_slice());
        MarketId::new(digest.into())
    }
}

#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AssetConfig {
    pub symbol: String,
    #[serde(serialize_with = "serialize_hex")]
    pub asset: AssetId,
    pub decimals: u8,
    pub min_precision: u8,
    pub max_precision: u8,
}

#[serde_as]
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize)]
pub struct OrderBookConfig {
    #[serde_as(as = "Option<HexDisplayFromStr>")]
    pub contract_id: Option<ContractId>,
    #[serde_as(as = "Option<HexDisplayFromStr>")]
    pub blob_id: Option<ContractId>,
    #[serde_as(as = "HexDisplayFromStr")]
    pub market_id: MarketId,
    #[serde_as(as = "DisplayFromStr")]
    pub taker_fee: u64,
    #[serde_as(as = "DisplayFromStr")]
    pub maker_fee: u64,
    #[serde_as(as = "DisplayFromStr")]
    pub min_order: u64,
    #[serde_as(as = "DisplayFromStr")]
    pub dust: u64,
    pub price_window: u8,
    pub base: AssetConfig,
    pub quote: AssetConfig,
}

#[derive(
    Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize,
)]
pub struct Fill {
    pub order_id: OrderId,
    pub quantity: Quantity,
    pub price: Price,
    pub timestamp: u128,
    pub fee: u64,
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum HistoryStep {
    Confirmed { tx_id: TxId },
}

impl HistoryStep {
    pub fn kind(&self) -> String {
        match self {
            HistoryStep::Confirmed { .. } => "confirmed".to_string(),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum OrderHistory {
    Created {
        tx: HistoryStep,
    },
    Trade {
        tx: HistoryStep,
        trade_id: TradeId,
    },
    Canceled {
        tx: HistoryStep,
        reason: CancellationReason,
    },
}

impl OrderHistory {
    pub fn kind(&self) -> String {
        match self {
            OrderHistory::Created { .. } => "created".to_string(),
            OrderHistory::Trade { .. } => "trade".to_string(),
            OrderHistory::Canceled { .. } => "canceled".to_string(),
        }
    }

    pub fn status_kind(&self) -> String {
        match self {
            OrderHistory::Created { tx } => tx.kind(),
            OrderHistory::Trade { tx, .. } => tx.kind(),
            OrderHistory::Canceled { tx, .. } => tx.kind(),
        }
    }

    pub fn tx_id(&self) -> TxId {
        match self {
            OrderHistory::Created { tx } => match tx {
                HistoryStep::Confirmed { tx_id } => *tx_id,
            },
            OrderHistory::Trade { tx, .. } => match tx {
                HistoryStep::Confirmed { tx_id } => *tx_id,
            },
            OrderHistory::Canceled { tx, .. } => match tx {
                HistoryStep::Confirmed { tx_id } => *tx_id,
            },
        }
    }
}

#[derive(
    Copy,
    Clone,
    Debug,
    strum_macros::EnumCount,
    strum_macros::IntoStaticStr,
    strum_macros::FromRepr,
    PartialEq,
    Eq,
    enum_iterator::Sequence,
    Hash,
)]
#[repr(u8)]
pub enum OrderStatus {
    Active,
    Canceled,
    Filled,
    PartiallyFilled,
}

impl TryFrom<u8> for OrderStatus {
    type Error = anyhow::Error;

    fn try_from(value: u8) -> Result<Self, Self::Error> {
        OrderStatus::from_repr(value).ok_or_else(|| {
            anyhow::anyhow!("Invalid value for OrderStatus enum: {}", value)
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Order {
    pub order_id: OrderId,
    pub account: Identity,
    pub side: Side,
    pub order_type: OrderType,
    pub price: Price,
    pub desired_quantity: UnderlyingAsset,
    pub fill: Vec<Fill>,
    pub timestamp: u128,
    pub order_tx_history: Vec<OrderHistory>,
    pub base_decimals: u64,
}

#[serde_as]
#[derive(Default, serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct OrderBookBalance {
    #[serde_as(as = "DisplayFromStr")]
    locked: u128,
    #[serde_as(as = "DisplayFromStr")]
    unlocked: u128,
    #[serde_as(as = "DisplayFromStr")]
    fee: u128,
}

impl OrderBookBalance {
    pub fn locked(&self) -> u128 {
        self.locked
    }

    pub fn unlocked(&self) -> u128 {
        self.unlocked
    }

    pub fn fee(&self) -> u128 {
        self.fee
    }
}

pub type OrderBooksBalances = BTreeMap<ContractId, OrderBookBalance>;
