use candid::{CandidType, Nat};
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};

use crate::{AssetAmount, AssetId, MarketId, WalletRegisterId};

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

    /// Orderbook maker fee
    pub maker_fee: BigUint,

    /// 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: BigUint,

    /// 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 num_bigint::BigUint;
/// use ex3_node_types::transaction::OrderPriceLimitation;
/// let latest_order_price: BigUint = BigUint::from(1000000000u32);
/// let order_price_limitation = OrderPriceLimitation {
///    bid_price_rate_cap: BigUint::from(300000000u32),
///    ask_price_rate_floor: BigUint::from(20000000u32),
///    rate_decimal: 8,
/// };
/// let real_bid_price_rate_cap  = order_price_limitation.bid_price_rate_cap.clone()
///         / BigUint::from(10u32.pow(order_price_limitation.rate_decimal.clone() as u32));
/// assert_eq!(real_bid_price_rate_cap, BigUint::from(3u32));
/// ```
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct OrderPriceLimitation {
    /// Orderbook biding price rate cap
    pub bid_price_rate_cap: BigUint,
    /// Orderbook asking price rate floor
    pub ask_price_rate_floor: BigUint,
    /// Price rate decimal
    pub rate_decimal: u8,
}

/// Market trading settings
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct MarketTradingSettings {
    /// 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: OrderPriceLimitation,
}

/// Register market
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct RegisterMarket {
    /// Base asset id
    pub base_asset_id: AssetId,

    /// Base asset quantity
    pub base_asset_quantity: AssetAmount,

    /// Quote asset id
    pub quote_asset_id: AssetId,

    /// Quote asset quantity
    pub quote_asset_quantity: AssetAmount,

    /// Market trading settings
    pub trading_settings: MarketTradingSettings,

    /// Trading fee settings
    /// Note: if the trading fee is none, the market trading fee will use the global trading fee
    pub fee: Option<MarketTradingFee>,

    /// Fee to
    /// Orderbook fee to & swap fee to use this value
    /// Note: fee to will receive the market trading fee
    /// Note: if the fee to is none, the market trading fee will use the global fee to
    pub fee_to: Option<WalletRegisterId>,
}

/// Update market trading settings
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct UpdateMarketTradingSettings {
    /// Market id
    pub market_id: MarketId,

    /// Market trading settings
    pub trading_settings: MarketTradingSettings,
}

/// Update market global fee to
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct UpdateMarketGlobalFeeTo {
    /// Fee to
    pub fee_to: WalletRegisterId,
}

/// Update market fee to
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct UpdateMarketFeeTo {
    /// Market id
    pub market_id: MarketId,

    /// Fee to
    /// Note: if the fee to is none, the market trading fee will use the global fee to
    pub fee_to: Option<WalletRegisterId>,
}

/// Update market global fee
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct UpdateMarketGlobalFee {
    /// Fee
    pub fee: MarketTradingFee,
}

/// Update market fee
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct UpdateMarketFee {
    /// Market id
    pub market_id: MarketId,

    /// Fee
    /// Note: if the fee is none, the market trading fee will use the global trading fee
    pub fee: Option<MarketTradingFee>,
}

#[derive(CandidType, Clone, Debug, Eq, PartialEq, Deserialize)]
pub struct CandidMarketTradingFee {
    /// 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,
}

impl From<CandidMarketTradingFee> for MarketTradingFee {
    fn from(candid_market_trading_fee: CandidMarketTradingFee) -> Self {
        MarketTradingFee {
            taker_fee: candid_market_trading_fee.taker_fee.0,
            maker_fee: candid_market_trading_fee.maker_fee.0,
            amm_v2_fee_taking_ratio: candid_market_trading_fee.amm_v2_fee_taking_ratio.0,
            fee_decimal: candid_market_trading_fee.fee_decimal,
        }
    }
}

impl From<MarketTradingFee> for CandidMarketTradingFee {
    fn from(market_trading_fee: MarketTradingFee) -> Self {
        CandidMarketTradingFee {
            taker_fee: Nat(market_trading_fee.taker_fee),
            maker_fee: Nat(market_trading_fee.maker_fee),
            amm_v2_fee_taking_ratio: Nat(market_trading_fee.amm_v2_fee_taking_ratio),
            fee_decimal: market_trading_fee.fee_decimal,
        }
    }
}

#[cfg(test)]
mod tests {
    use ex3_serde::{bincode, cbor};

    use super::*;

    #[test]
    fn test_register_market_serde() {
        let register_market = RegisterMarket {
            base_asset_id: AssetId::from(1u32),
            base_asset_quantity: AssetAmount::from(100u32),
            quote_asset_id: AssetId::from(2u32),
            quote_asset_quantity: AssetAmount::from(200u32),
            trading_settings: MarketTradingSettings {
                price_max_decimal: 8,
                volume_max_decimal: 8,
                min_order_amount: AssetAmount::from(300u32),
                min_order_volume: AssetAmount::from(400u32),
                price_limitation: OrderPriceLimitation {
                    bid_price_rate_cap: BigUint::from(100u32),
                    ask_price_rate_floor: BigUint::from(100u32),
                    rate_decimal: 8,
                },
            },
            fee: Some(MarketTradingFee {
                taker_fee: BigUint::from(300u32),
                maker_fee: BigUint::from(500u32),
                amm_v2_fee_taking_ratio: BigUint::from(500u32),
                fee_decimal: 8,
            }),
            fee_to: Some(WalletRegisterId::from(1u32)),
        };

        // bincode
        let bytes = bincode::serialize(&register_market).unwrap();
        let register_market_deserialized: RegisterMarket = bincode::deserialize(&bytes).unwrap();
        assert_eq!(register_market, register_market_deserialized);

        // cbor
        let bytes = cbor::serialize(&register_market).unwrap();
        let register_market_deserialized: RegisterMarket = cbor::deserialize(&bytes).unwrap();
        assert_eq!(register_market, register_market_deserialized);
    }

    #[test]
    fn test_update_market_settings_serde() {
        let update_market_settings = UpdateMarketTradingSettings {
            market_id: MarketId::from(1u32),
            trading_settings: MarketTradingSettings {
                price_max_decimal: 8,
                volume_max_decimal: 8,
                min_order_amount: AssetAmount::from(100u32),
                min_order_volume: AssetAmount::from(100u32),
                price_limitation: OrderPriceLimitation {
                    bid_price_rate_cap: BigUint::from(100u32),
                    ask_price_rate_floor: BigUint::from(100u32),
                    rate_decimal: 8,
                },
            },
        };

        // bincode
        let bytes = bincode::serialize(&update_market_settings).unwrap();
        let update_market_settings_deserialized: UpdateMarketTradingSettings =
            bincode::deserialize(&bytes).unwrap();
        assert_eq!(update_market_settings, update_market_settings_deserialized);

        // cbor
        let bytes = cbor::serialize(&update_market_settings).unwrap();
        let update_market_settings_deserialized: UpdateMarketTradingSettings =
            cbor::deserialize(&bytes).unwrap();
        assert_eq!(update_market_settings, update_market_settings_deserialized);
    }

    #[test]
    fn test_update_global_market_fee_to_serde() {
        let update_global_market_fee_to = UpdateMarketGlobalFeeTo {
            fee_to: WalletRegisterId::from(1u32),
        };

        // bincode
        let bytes = bincode::serialize(&update_global_market_fee_to).unwrap();
        let update_global_market_fee_to_deserialized: UpdateMarketGlobalFeeTo =
            bincode::deserialize(&bytes).unwrap();
        assert_eq!(
            update_global_market_fee_to,
            update_global_market_fee_to_deserialized
        );

        // cbor
        let bytes = cbor::serialize(&update_global_market_fee_to).unwrap();
        let update_global_market_fee_to_deserialized: UpdateMarketGlobalFeeTo =
            cbor::deserialize(&bytes).unwrap();
        assert_eq!(
            update_global_market_fee_to,
            update_global_market_fee_to_deserialized
        );
    }

    #[test]
    fn test_update_market_fee_to_serde() {
        let update_market_fee_to_1 = UpdateMarketFeeTo {
            market_id: MarketId::from(1u32),
            fee_to: Some(WalletRegisterId::from(1u32)),
        };

        // bincode
        let bytes = bincode::serialize(&update_market_fee_to_1).unwrap();
        let update_market_fee_to_deserialized: UpdateMarketFeeTo =
            bincode::deserialize(&bytes).unwrap();
        assert_eq!(update_market_fee_to_1, update_market_fee_to_deserialized);

        // cbor
        let bytes = cbor::serialize(&update_market_fee_to_1).unwrap();
        let update_market_fee_to_deserialized: UpdateMarketFeeTo =
            cbor::deserialize(&bytes).unwrap();
        assert_eq!(update_market_fee_to_1, update_market_fee_to_deserialized);

        let update_market_fee_to_2 = UpdateMarketFeeTo {
            market_id: MarketId::from(1u32),
            fee_to: Some(WalletRegisterId::from(1u32)),
        };

        // bincode
        let bytes = bincode::serialize(&update_market_fee_to_2).unwrap();
        let update_market_fee_to_deserialized: UpdateMarketFeeTo =
            bincode::deserialize(&bytes).unwrap();
        assert_eq!(update_market_fee_to_2, update_market_fee_to_deserialized);

        // cbor
        let bytes = cbor::serialize(&update_market_fee_to_2).unwrap();
        let update_market_fee_to_deserialized: UpdateMarketFeeTo =
            cbor::deserialize(&bytes).unwrap();
        assert_eq!(update_market_fee_to_2, update_market_fee_to_deserialized);
    }

    #[test]
    fn test_update_global_market_fee_serde() {
        let update_global_market_fee = UpdateMarketGlobalFee {
            fee: MarketTradingFee {
                taker_fee: BigUint::from(100u32),
                maker_fee: BigUint::from(200u32),
                fee_decimal: 8,
                amm_v2_fee_taking_ratio: BigUint::from(300u32),
            },
        };

        // bincode
        let bytes = bincode::serialize(&update_global_market_fee).unwrap();
        let update_global_market_fee_deserialized: UpdateMarketGlobalFee =
            bincode::deserialize(&bytes).unwrap();
        assert_eq!(
            update_global_market_fee,
            update_global_market_fee_deserialized
        );

        // cbor
        let bytes = cbor::serialize(&update_global_market_fee).unwrap();
        let update_global_market_fee_deserialized: UpdateMarketGlobalFee =
            cbor::deserialize(&bytes).unwrap();
        assert_eq!(
            update_global_market_fee,
            update_global_market_fee_deserialized
        );
    }

    #[test]
    fn test_update_market_fee_serde() {
        let update_market_fee_1 = UpdateMarketFee {
            market_id: MarketId::from(1u32),
            fee: Some(MarketTradingFee {
                taker_fee: BigUint::from(100u32),
                maker_fee: BigUint::from(200u32),
                fee_decimal: 8,
                amm_v2_fee_taking_ratio: BigUint::from(200u32),
            }),
        };

        // bincode
        let bytes = bincode::serialize(&update_market_fee_1).unwrap();
        let update_market_fee_deserialized: UpdateMarketFee = bincode::deserialize(&bytes).unwrap();
        assert_eq!(update_market_fee_1, update_market_fee_deserialized);

        // cbor
        let bytes = cbor::serialize(&update_market_fee_1).unwrap();
        let update_market_fee_deserialized: UpdateMarketFee = cbor::deserialize(&bytes).unwrap();
        assert_eq!(update_market_fee_1, update_market_fee_deserialized);

        let update_market_fee_2 = UpdateMarketFee {
            market_id: MarketId::from(1u32),
            fee: None,
        };

        // bincode
        let bytes = bincode::serialize(&update_market_fee_2).unwrap();
        let update_market_fee_deserialized: UpdateMarketFee = bincode::deserialize(&bytes).unwrap();
        assert_eq!(update_market_fee_2, update_market_fee_deserialized);

        // cbor
        let bytes = cbor::serialize(&update_market_fee_2).unwrap();
        let update_market_fee_deserialized: UpdateMarketFee = cbor::deserialize(&bytes).unwrap();
        assert_eq!(update_market_fee_2, update_market_fee_deserialized);
    }
}
