use super::{OrderScope, OrderSide};
use crate::{AssetAmount, SpotMarketId};
use serde::{Deserialize, Serialize};

/// Spot order detail
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct SpotOrderDetail {
    /// Order side
    pub side: OrderSide,

    /// Order price
    pub price: AssetAmount,

    /// Order volume
    /// Note: volume means base asset quantity
    pub volume: AssetAmount,
}

/// Amm V2 exact tokens
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub enum AmmV2ExactTokens {
    /// Exact input
    ExactInput {
        /// Input amount
        /// Note: input amount means the input asset quantity
        amount: AssetAmount,
        /// Min out
        /// Note: min out means the output asset quantity must be greater than or equal to the min out
        min_out: AssetAmount,
    },

    /// Exact output
    ExactOutput {
        /// Output amount
        /// Note: output amount means the output asset quantity
        amount: AssetAmount,
        /// Max in
        /// Note: max in means the input asset quantity must be less than or equal to the max in
        max_in: AssetAmount,
    },
}

/// Amm V2 order detail
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct AmmV2OrderDetail {
    /// Order side
    pub side: OrderSide,

    /// Exact tokens
    pub exact_tokens: AmmV2ExactTokens,
}

/// Order detail
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub enum SpotOrder {
    /// Limit order detail
    LimitOrder(SpotOrderDetail),

    /// Amm V2 order detail
    AmmV2(AmmV2OrderDetail),
}

/// Submit spot order
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct SubmitSpotOrder {
    /// Market id
    pub market_id: SpotMarketId,

    /// Order
    pub order: SpotOrder,
}

/// Cancel spot order
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct CancelSpotOrder {
    /// Market id
    pub market_id: SpotMarketId,

    /// Cancel order scope
    pub scope: OrderScope,
}

/// Add AMM V2 liquidity
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct AddAmmV2Liquidity {
    /// Market id
    pub market_id: SpotMarketId,

    /// Base asset quantity
    pub base_asset_quantity: AssetAmount,

    /// Quote asset quantity
    pub quote_asset_quantity: AssetAmount,
}

/// Remove liquidity
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct RemoveAmmV2Liquidity {
    /// Market id
    pub market_id: SpotMarketId,

    /// Liquidity token quantity
    pub liquidity_token_quantity: AssetAmount,
}

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

    #[test]
    fn test_submit_order_serde() {
        let submit_order = SubmitSpotOrder {
            market_id: SpotMarketId::from(1u8),
            order: SpotOrder::LimitOrder(SpotOrderDetail {
                side: OrderSide::Ask,
                price: 100u32.into(),
                volume: 100u32.into(),
            }),
        };

        // bincode
        let encoded = bincode::serialize(&submit_order).unwrap();
        let decoded: SubmitSpotOrder = bincode::deserialize(&encoded).unwrap();
        assert_eq!(submit_order, decoded);

        // cbor
        let encoded = cbor::serialize(&submit_order).unwrap();
        let decoded: SubmitSpotOrder = cbor::deserialize(&encoded).unwrap();
        assert_eq!(submit_order, decoded);
    }

    #[test]
    fn test_cancel_order_serde() {
        let cancel_order = CancelSpotOrder {
            market_id: SpotMarketId::from(1u8),
            scope: OrderScope::Exact(OrderId::from(1u8)),
        };

        // bincode
        let encoded = bincode::serialize(&cancel_order).unwrap();
        let decoded: CancelSpotOrder = bincode::deserialize(&encoded).unwrap();
        assert_eq!(cancel_order, decoded);

        // cbor
        let encoded = cbor::serialize(&cancel_order).unwrap();
        let decoded: CancelSpotOrder = cbor::deserialize(&encoded).unwrap();
        assert_eq!(cancel_order, decoded);
    }

    #[test]
    fn test_add_amm_v2_liquidity_serde() {
        let add_liquidity = AddAmmV2Liquidity {
            market_id: SpotMarketId::from(1u8),
            base_asset_quantity: AssetAmount::from(1u8),
            quote_asset_quantity: AssetAmount::from(1u8),
        };

        // bincode
        let encoded = bincode::serialize(&add_liquidity).unwrap();
        let decoded: AddAmmV2Liquidity = bincode::deserialize(&encoded).unwrap();
        assert_eq!(add_liquidity, decoded);

        // cbor
        let encoded = cbor::serialize(&add_liquidity).unwrap();
        let decoded: AddAmmV2Liquidity = cbor::deserialize(&encoded).unwrap();
        assert_eq!(add_liquidity, decoded);
    }

    #[test]
    fn test_remove_amm_v2_liquidity_serde() {
        let remove_liquidity = RemoveAmmV2Liquidity {
            market_id: SpotMarketId::from(1u8),
            liquidity_token_quantity: AssetAmount::from(1u8),
        };

        // bincode
        let encoded = bincode::serialize(&remove_liquidity).unwrap();
        let decoded: RemoveAmmV2Liquidity = bincode::deserialize(&encoded).unwrap();
        assert_eq!(remove_liquidity, decoded);

        // cbor
        let encoded = cbor::serialize(&remove_liquidity).unwrap();
        let decoded: RemoveAmmV2Liquidity = cbor::deserialize(&encoded).unwrap();
        assert_eq!(remove_liquidity, decoded);
    }
}
