use std::ops::{Div, Mul};

use ex3_canister_types::chain::Chain;
use num_bigint::BigUint;
use serde::{Deserialize, Serialize};

use crate::asset::TokenType;
use crate::AssetId;

/// Register an asset
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct RegisterAsset {
    /// Specify the blockchain
    pub chain: Chain,
    /// Specify the network
    pub network: u8,
    /// Specify the type of token
    pub r#type: TokenType,
    /// Specify the parent asset ID
    pub parent_asset_id: Option<AssetId>,
    /// Specify the address of the asset
    pub address: String,
}

/// Update the confirmation times for a token type on a specific network and chain
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct UpdateTokenTypeConfirmationTimes {
    /// The chain this token type belongs to
    pub chain: Chain,
    /// The network this token type belongs to            
    pub network: u8,
    /// The confirmation time of the tx of this token type
    pub confirmation_times: BigUint,
}

/// Withdrawal fee
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct WithdrawalFee {
    // Fee rate
    pub fee_rate: BigUint,
    // Decimal of the fee rate
    pub rate_decimal: u8,
    // Minimum fee allowed
    pub min_fee: BigUint,
}
impl WithdrawalFee {
    /// Calculate fee
    #[allow(dead_code)]
    pub fn calc_fee(&self, amount: &BigUint) -> BigUint {
        let fee = amount
            .mul(&self.fee_rate)
            .div(&BigUint::from(10u32).pow(self.rate_decimal.clone() as u32));
        if fee < self.min_fee {
            self.min_fee.clone()
        } else {
            fee
        }
    }
}

/// Update asset withdrawal fee
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct UpdateAssetWithdrawalFee {
    /// Asset ID
    pub asset_id: AssetId,
    /// Withdrawal fee
    pub withdrawal_fee: WithdrawalFee,
}

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

    use crate::asset::EVMTokenType::ERC20;

    use super::*;

    #[test]
    fn test_register_asset_bincode_serde() {
        let register_asset = RegisterAsset {
            chain: Chain::Ethereum,
            network: 0,
            r#type: TokenType::EVM(ERC20),
            parent_asset_id: None,
            address: "0x01283012830123dfsf".to_string(),
        };

        let encoded = bincode::serialize(&register_asset).unwrap();
        let decoded: RegisterAsset = bincode::deserialize(&encoded).unwrap();
        assert_eq!(register_asset, decoded);
    }

    #[test]
    fn test_register_asset_cbor_serde() {
        let register_asset = RegisterAsset {
            chain: Chain::Ethereum,
            network: 0,
            r#type: TokenType::EVM(ERC20),
            parent_asset_id: None,
            address: "0x01283012830123dfsf".to_string(),
        };

        let encoded = cbor::serialize(&register_asset).unwrap();
        let decoded: RegisterAsset = cbor::deserialize(&encoded).unwrap();
        assert_eq!(register_asset, decoded);
    }

    #[test]
    fn test_update_token_type_confirmation_times_bincode_serde() {
        let update_token_type_confirmation_times = UpdateTokenTypeConfirmationTimes {
            chain: Chain::Ethereum,
            network: 0,
            confirmation_times: BigUint::from(100u32),
        };

        let encoded = bincode::serialize(&update_token_type_confirmation_times).unwrap();
        let decoded: UpdateTokenTypeConfirmationTimes = bincode::deserialize(&encoded).unwrap();
        assert_eq!(update_token_type_confirmation_times, decoded);
    }

    #[test]
    fn test_update_token_type_confirmation_times_cbor_serde() {
        let update_token_type_confirmation_times = UpdateTokenTypeConfirmationTimes {
            chain: Chain::Ethereum,
            network: 0,
            confirmation_times: BigUint::from(100u32),
        };

        let encoded = cbor::serialize(&update_token_type_confirmation_times).unwrap();
        let decoded: UpdateTokenTypeConfirmationTimes = cbor::deserialize(&encoded).unwrap();
        assert_eq!(update_token_type_confirmation_times, decoded);
    }

    #[test]

    fn test_update_asset_withdrawal_fee_bincode_serde() {
        let update_asset_withdrawal_fee = UpdateAssetWithdrawalFee {
            asset_id: AssetId::from(301u16),
            withdrawal_fee: WithdrawalFee {
                fee_rate: BigUint::from(100u32),
                rate_decimal: 2,
                min_fee: BigUint::from(100u32),
            },
        };

        let encoded = bincode::serialize(&update_asset_withdrawal_fee).unwrap();
        let decoded: UpdateAssetWithdrawalFee = bincode::deserialize(&encoded).unwrap();
        assert_eq!(update_asset_withdrawal_fee, decoded);
    }

    #[test]
    fn test_update_asset_withdrawal_fee_cbor_serde() {
        let update_asset_withdrawal_fee = UpdateAssetWithdrawalFee {
            asset_id: AssetId::from(301u16),
            withdrawal_fee: WithdrawalFee {
                fee_rate: BigUint::from(100u32),
                rate_decimal: 2,
                min_fee: BigUint::from(100u32),
            },
        };

        let encoded = cbor::serialize(&update_asset_withdrawal_fee).unwrap();
        let decoded: UpdateAssetWithdrawalFee = cbor::deserialize(&encoded).unwrap();
        assert_eq!(update_asset_withdrawal_fee, decoded);
    }
}
