use crate::chain::Chain;
use crate::sign_schema::SignatureSchema;
use crate::{AssetAmount, AssetId};
use ex3_serde::bincode;
use ex3_timestamp::TimeInNs;
use ic_stable_structures::storable::Bound;
use ic_stable_structures::Storable;
use serde::{Deserialize, Serialize};

/// Withdrawal
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
pub struct Withdrawal {
    /// Specify chain
    pub chain: Chain,

    /// Signature schema
    pub sign_schema: SignatureSchema,

    /// Withdrawal to address
    pub to: String,

    /// Withdrawal asset id
    pub asset_id: AssetId,

    /// Withdrawal amount
    /// Note: This is the amount that the user will receive
    pub amount: AssetAmount,

    /// Withdrawal fee
    pub fee: AssetAmount,
}

/// Force withdrawal
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct ForceWithdrawal {
    /// Specify chain
    pub chain: Chain,

    /// Signature schema
    pub sign_schema: SignatureSchema,

    /// Withdrawal to address
    pub to: String,

    /// Withdrawal asset id
    pub asset_id: AssetId,

    /// Withdrawal amount
    /// Note: This is the amount that the user will receive
    pub amount: AssetAmount,

    /// Withdrawal fee
    pub fee: AssetAmount,

    /// Timestamp
    pub timestamp: TimeInNs,
}

impl Storable for Withdrawal {
    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).unwrap()
    }

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

impl Storable for ForceWithdrawal {
    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).unwrap()
    }

    const BOUND: Bound = Bound::Bounded {
        max_size: 297,
        is_fixed_size: false,
    };
}
#[cfg(test)]
mod tests {
    use crate::chain::ChainType;
    use ex3_serde::{bincode, cbor};

    use super::*;

    #[test]
    fn test_serde() {
        let withdrawal = Withdrawal {
            chain: Chain {
                r#type: ChainType::Ethereum,
                network: 1u8.into(),
            },
            sign_schema: SignatureSchema::EvmEcdsa,
            to: "0x0000001312312321313213".to_string(),
            asset_id: AssetId::from(2000u32),
            amount: AssetAmount::from(10000u64),
            fee: AssetAmount::from(100u64),
        };

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

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

    #[test]
    fn test_storable_for_withdrawal() {
        let withdrawal = Withdrawal {
            chain: Chain {
                r#type: ChainType::Ethereum,
                network: 1u8.into(),
            },
            sign_schema: SignatureSchema::EvmEcdsa,
            to: "0x3f349bBaFEc1551819B8be1EfEA2fC46cA749aA1".to_string(),
            asset_id: AssetId::from(u128::MAX),
            amount: AssetAmount::from(u128::MAX),
            fee: AssetAmount::from(u128::MAX),
        };

        let bytes = withdrawal.to_bytes();
        assert!(bytes.len() <= 289, "bytes.len() = {}", bytes.len());
        let decoded = Withdrawal::from_bytes(bytes);
        assert_eq!(withdrawal, decoded);
    }

    #[test]
    fn test_storable_for_force_withdrawal() {
        let force_withdrawal = ForceWithdrawal {
            chain: Chain {
                r#type: ChainType::Ethereum,
                network: 1u8.into(),
            },
            sign_schema: SignatureSchema::EvmEcdsa,
            to: "0x3f349bBaFEc1551819B8be1EfEA2fC46cA749aA1".to_string(),
            asset_id: AssetId::from(u128::MAX),
            amount: AssetAmount::from(u128::MAX),
            fee: AssetAmount::from(u128::MAX),
            timestamp: TimeInNs(u64::MAX),
        };

        let bytes = force_withdrawal.to_bytes();
        assert!(bytes.len() <= 297, "bytes.len() = {}", bytes.len());
        let decoded = ForceWithdrawal::from_bytes(bytes);
        assert_eq!(force_withdrawal, decoded);
    }
}
