use crate::impl_from_uint_for_enum;
use candid::{CandidType, Deserialize};
use ex3_canister_types::chain::Chain;
use ex3_node_error::OtherError;
use ex3_serde::bincode::{deserialize, serialize};
use num_bigint::BigUint;
use serde::Serialize;

/// Token type
#[derive(CandidType, Debug, Clone, Hash, Deserialize, Serialize, PartialEq, Eq)]
pub enum TokenType {
    /// Native asset of the chain
    /// # Example
    /// * Bitcoin, BTC
    /// * Ethereum, ETH
    Native = 0,

    /// Native gas asset of the chain
    /// # Example
    /// * Neo, GAS
    /// * Dfinity, Cycles
    NativeGas = 1,

    /// EVM fungible token type
    ERC20 = 3,

    /// EVM non-fungible token type
    ERC721 = 4,

    /// Dfinity token [DFT] standard
    DFT = 5,

    /// Dfinity token [ICRC1] standard
    ICRC1 = 6,
}

/// crypto asset
#[derive(CandidType, Debug, Clone, Hash, PartialEq, Eq)]
pub struct CryptoAsset {
    pub chain: Chain,
    pub r#type: TokenType,
    pub address: Option<String>,
}

impl CryptoAsset {
    pub fn encode(&self) -> Vec<u8> {
        serialize(&(&self.chain, &self.r#type, &self.address)).unwrap()
    }
}

impl TryFrom<&[u8]> for CryptoAsset {
    type Error = OtherError;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        let (chain, token_type, token_id) = deserialize(value).map_err(|e| {
            OtherError::new(format!(
                "Failed to deserialize CryptoAsset from bytes: {:?}",
                e
            ))
        })?;
        Ok(CryptoAsset {
            chain,
            r#type: token_type,
            address: token_id,
        })
    }
}

impl From<BigUint> for TokenType {
    fn from(token_type: BigUint) -> Self {
        match token_type {
            token_type if token_type == TokenType::Native.into() => TokenType::Native,
            token_type if token_type == TokenType::NativeGas.into() => TokenType::NativeGas,
            token_type if token_type == TokenType::ERC20.into() => TokenType::ERC20,
            token_type if token_type == TokenType::ERC721.into() => TokenType::ERC721,
            token_type if token_type == TokenType::DFT.into() => TokenType::DFT,
            token_type if token_type == TokenType::ICRC1.into() => TokenType::ICRC1,
            _ => panic!("Invalid token type: {}", token_type),
        }
    }
}

impl Into<BigUint> for TokenType {
    fn into(self) -> BigUint {
        BigUint::from(self as u128)
    }
}

impl_from_uint_for_enum!(TokenType, u8, u16, u32, u64, u128);
