use candid::{CandidType, Deserialize, Nat};
use ex3_serde::bincode;
use ic_stable_structures::{storable::Bound, Storable};
use serde::Serialize;

use crate::{
    number::Ex3Uint, AssetAmount, AssetId, BlockHeight, CandidAssetAmount, CandidAssetId,
    CandidBlockHeight, CandidSpotMarketId, CandidWalletRegisterId, SpotMarketId, WalletRegisterId,
};

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SpotMarketIdentifier {
    /// Base asset id
    pub base_asset_id: AssetId,

    /// Quote asset id
    pub quote_asset_id: AssetId,
}

impl Storable for SpotMarketIdentifier {
    fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
        bincode::serialize(self).unwrap().into()
    }

    fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
        bincode::deserialize(bytes.as_ref()).unwrap()
    }

    const BOUND: Bound = Bound::Bounded {
        max_size: 64,
        is_fixed_size: false,
    };
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct SpotMarket {
    /// Spot market identifier
    pub identifier: SpotMarketIdentifier,

    /// Market trading settings
    pub trading_settings: SpotMarketTradingSettings,

    /// Fee
    /// Trading fee
    pub fee: SpotMarketTradingFee,

    /// Fee to
    /// Trading fee to
    pub fee_to: WalletRegisterId,
}

#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
pub struct RegisteredSpotMarket {
    /// Spot market id
    pub id: SpotMarketId,

    /// Spot market
    pub market: SpotMarket,

    /// Registered height
    pub registered_height: BlockHeight,
}

#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct SpotMarketTradingFee {
    /// Orderbook taker fee
    /// Swap fee is the same as orderbook taker fee
    pub taker_fee: Ex3Uint,

    /// Orderbook maker fee
    pub maker_fee: Ex3Uint,

    /// AMM V2 fee taking ratio
    /// # Range
    /// 0 ~ 100
    /// # Example
    /// * 0: 0%
    /// * 100: 100%
    /// # Note
    /// This value is used for AMM V2
    pub amm_v2_fee_taking_ratio: Ex3Uint,

    /// Fee decimal for
    /// * `taker_fee` & `maker_fee` & `amm_v2_fee_taking_ratio`
    pub fee_decimal: u8,
}

/// Orderbook price limitation
/// # Example
/// ```rust
/// use std::ops::Mul;
/// use ex3_node_types::number::Ex3Uint;
/// use ex3_node_types::market::SpotOrderPriceLimitation;
/// let order_price_limitation = SpotOrderPriceLimitation {
///    bid_price_rate_cap: Ex3Uint::from(300000000u32),
///    ask_price_rate_floor: Ex3Uint::from(20000000u32),
///    rate_decimal: 8,
/// };
/// let real_bid_price_rate_cap  = order_price_limitation.bid_price_rate_cap.clone()
///         / Ex3Uint::from(10u32.pow(order_price_limitation.rate_decimal.clone() as u32));
/// assert_eq!(real_bid_price_rate_cap, Ex3Uint::from(3u32));
/// ```
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct SpotOrderPriceLimitation {
    /// Orderbook biding price rate cap
    pub bid_price_rate_cap: Ex3Uint,
    /// Orderbook asking price rate floor
    pub ask_price_rate_floor: Ex3Uint,
    /// Price rate decimal
    pub rate_decimal: u8,
}

/// Market trading settings
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct SpotMarketTradingSettings {
    /// Price max decimal
    /// Orderbook price decimal & swap price decimal use this value
    pub price_max_decimal: u8,

    /// Volume max decimal
    /// Orderbook volume decimal & swap volume decimal use this value
    /// Note: volume means base asset quantity
    pub volume_max_decimal: u8,

    /// Min order amount
    /// Orderbook min order amount & swap min order amount use this value
    /// Note: amount means quota asset quantity
    pub min_order_amount: AssetAmount,

    /// Min order volume
    /// Orderbook min order volume & swap min order volume use this value
    /// Note: volume means base asset quantity
    pub min_order_volume: AssetAmount,

    /// Price limitation
    pub price_limitation: SpotOrderPriceLimitation,
}

#[derive(CandidType, Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct CandidSpotMarketTradingFee {
    /// Orderbook taker fee
    /// Swap fee is the same as orderbook taker fee
    pub taker_fee: Nat,

    /// Orderbook maker fee
    pub maker_fee: Nat,

    /// AMM V2 fee taking ratio
    /// # Range
    /// 0 ~ 100
    /// # Example
    /// * 0: 0%
    /// * 100: 100%
    /// # Note
    /// This value is used for AMM V2
    pub amm_v2_fee_taking_ratio: Nat,

    /// Fee decimal for
    /// * `taker_fee` & `maker_fee` & `amm_v2_fee_taking_ratio`
    pub fee_decimal: u8,
}

#[derive(CandidType, Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct CandidSpotOrderPriceLimitation {
    /// Orderbook biding price rate cap
    pub bid_price_rate_cap: Nat,
    /// Orderbook asking price rate floor
    pub ask_price_rate_floor: Nat,
    /// Price rate decimal
    pub rate_decimal: u8,
}

#[derive(CandidType, Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct CandidSpotMarketTradingSettings {
    /// Price max decimal
    /// Orderbook price decimal & swap price decimal use this value
    pub price_max_decimal: u8,

    /// Volume max decimal
    /// Orderbook volume decimal & swap volume decimal use this value
    /// Note: volume means base asset quantity
    pub volume_max_decimal: u8,

    /// Min order amount
    /// Orderbook min order amount & swap min order amount use this value
    /// Note: amount means quota asset quantity
    pub min_order_amount: CandidAssetAmount,

    /// Min order volume
    /// Orderbook min order volume & swap min order volume use this value
    /// Note: volume means base asset quantity
    pub min_order_volume: CandidAssetAmount,

    /// Price limitation
    pub price_limitation: CandidSpotOrderPriceLimitation,
}

#[derive(CandidType, Clone, Debug, Eq, PartialEq, Deserialize, PartialOrd, Ord, Hash)]
pub struct CandidSpotMarketIdentifier {
    /// Base asset id
    pub base_asset_id: CandidAssetId,

    /// Quote asset id
    pub quote_asset_id: CandidAssetId,
}

#[derive(CandidType, Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct CandidSpotMarket {
    /// Identifier
    pub identifier: CandidSpotMarketIdentifier,

    /// Market trading settings
    pub trading_settings: CandidSpotMarketTradingSettings,

    /// Market trading fee
    pub fee: CandidSpotMarketTradingFee,

    /// Market fee to
    /// Trading fee to
    pub fee_to: CandidWalletRegisterId,
}

#[derive(CandidType, Debug, Clone, Deserialize, PartialEq, Eq)]
pub struct CandidRegisteredSpotMarket {
    /// Spot market id
    pub id: CandidSpotMarketId,

    /// Spot market
    pub market: CandidSpotMarket,

    /// Registered height
    pub registered_height: CandidBlockHeight,
}

impl From<CandidSpotMarketIdentifier> for SpotMarketIdentifier {
    fn from(candid_market_identifier: CandidSpotMarketIdentifier) -> Self {
        SpotMarketIdentifier {
            base_asset_id: candid_market_identifier.base_asset_id.into(),
            quote_asset_id: candid_market_identifier.quote_asset_id.into(),
        }
    }
}

impl From<SpotMarketIdentifier> for CandidSpotMarketIdentifier {
    fn from(market_identifier: SpotMarketIdentifier) -> Self {
        CandidSpotMarketIdentifier {
            base_asset_id: market_identifier.base_asset_id.into(),
            quote_asset_id: market_identifier.quote_asset_id.into(),
        }
    }
}

impl From<CandidSpotMarketTradingFee> for SpotMarketTradingFee {
    fn from(candid_market_trading_fee: CandidSpotMarketTradingFee) -> Self {
        SpotMarketTradingFee {
            taker_fee: candid_market_trading_fee.taker_fee.into(),
            maker_fee: candid_market_trading_fee.maker_fee.into(),
            amm_v2_fee_taking_ratio: candid_market_trading_fee.amm_v2_fee_taking_ratio.into(),
            fee_decimal: candid_market_trading_fee.fee_decimal,
        }
    }
}

impl From<SpotMarketTradingFee> for CandidSpotMarketTradingFee {
    fn from(market_trading_fee: SpotMarketTradingFee) -> Self {
        CandidSpotMarketTradingFee {
            taker_fee: market_trading_fee.taker_fee.into(),
            maker_fee: market_trading_fee.maker_fee.into(),
            amm_v2_fee_taking_ratio: market_trading_fee.amm_v2_fee_taking_ratio.into(),
            fee_decimal: market_trading_fee.fee_decimal,
        }
    }
}

impl From<CandidSpotOrderPriceLimitation> for SpotOrderPriceLimitation {
    fn from(candid_order_price_limitation: CandidSpotOrderPriceLimitation) -> Self {
        SpotOrderPriceLimitation {
            bid_price_rate_cap: candid_order_price_limitation.bid_price_rate_cap.into(),
            ask_price_rate_floor: candid_order_price_limitation.ask_price_rate_floor.into(),
            rate_decimal: candid_order_price_limitation.rate_decimal,
        }
    }
}

impl From<SpotOrderPriceLimitation> for CandidSpotOrderPriceLimitation {
    fn from(order_price_limitation: SpotOrderPriceLimitation) -> Self {
        CandidSpotOrderPriceLimitation {
            bid_price_rate_cap: order_price_limitation.bid_price_rate_cap.into(),
            ask_price_rate_floor: order_price_limitation.ask_price_rate_floor.into(),
            rate_decimal: order_price_limitation.rate_decimal,
        }
    }
}

impl From<CandidSpotMarketTradingSettings> for SpotMarketTradingSettings {
    fn from(candid_market_trading_settings: CandidSpotMarketTradingSettings) -> Self {
        SpotMarketTradingSettings {
            price_max_decimal: candid_market_trading_settings.price_max_decimal,
            volume_max_decimal: candid_market_trading_settings.volume_max_decimal,
            min_order_amount: candid_market_trading_settings.min_order_amount.into(),
            min_order_volume: candid_market_trading_settings.min_order_volume.into(),
            price_limitation: candid_market_trading_settings.price_limitation.into(),
        }
    }
}

impl From<SpotMarketTradingSettings> for CandidSpotMarketTradingSettings {
    fn from(market_trading_settings: SpotMarketTradingSettings) -> Self {
        CandidSpotMarketTradingSettings {
            price_max_decimal: market_trading_settings.price_max_decimal,
            volume_max_decimal: market_trading_settings.volume_max_decimal,
            min_order_amount: market_trading_settings.min_order_amount.into(),
            min_order_volume: market_trading_settings.min_order_volume.into(),
            price_limitation: market_trading_settings.price_limitation.into(),
        }
    }
}

impl From<CandidSpotMarket> for SpotMarket {
    fn from(candid_market: CandidSpotMarket) -> Self {
        SpotMarket {
            identifier: candid_market.identifier.into(),
            trading_settings: candid_market.trading_settings.into(),
            fee: candid_market.fee.into(),
            fee_to: candid_market.fee_to.into(),
        }
    }
}

impl From<SpotMarket> for CandidSpotMarket {
    fn from(market: SpotMarket) -> Self {
        CandidSpotMarket {
            identifier: market.identifier.into(),
            trading_settings: market.trading_settings.into(),
            fee: market.fee.into(),
            fee_to: market.fee_to.into(),
        }
    }
}

impl From<RegisteredSpotMarket> for CandidRegisteredSpotMarket {
    fn from(registered_spot_market: RegisteredSpotMarket) -> Self {
        CandidRegisteredSpotMarket {
            id: registered_spot_market.id.into(),
            market: registered_spot_market.market.into(),
            registered_height: registered_spot_market.registered_height.into(),
        }
    }
}

impl From<CandidRegisteredSpotMarket> for RegisteredSpotMarket {
    fn from(candid_registered_spot_market: CandidRegisteredSpotMarket) -> Self {
        RegisteredSpotMarket {
            id: candid_registered_spot_market.id.into(),
            market: candid_registered_spot_market.market.into(),
            registered_height: candid_registered_spot_market.registered_height.into(),
        }
    }
}
