use std::fmt;

use ciborium::tag::Captured;
use ex3_canister_types::chain::Chain;
use ex3_node_error::OtherError;
use ex3_payload_derive::Ex3Payload;
use ex3_serde::{bincode, cbor};
use num_bigint::BigUint;
use serde::de::{Deserializer, SeqAccess, Visitor};
use serde::ser::{SerializeSeq, Serializer};
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;

use crate::{AssetAmount, AssetId};

#[derive(Debug, Clone, PartialEq, Eq, Ex3Payload)]
pub struct Withdrawal {
    #[index(0)]
    pub chain: Chain,
    #[index(1)]
    pub network: u8,
    #[index(2)]
    pub address: String,
    #[index(3)]
    #[alias_of_big_uint]
    pub asset: AssetId,
    #[index(4)]
    #[alias_of_big_uint]
    pub amount: AssetAmount,
}

impl Withdrawal {
    pub fn encode(&self) -> Vec<u8> {
        bincode::serialize(&(
            &self.chain,
            &self.network,
            &self.address,
            &self.asset,
            &self.amount,
        ))
        .unwrap()
    }

    pub fn decode(data: &[u8]) -> Result<Self, OtherError> {
        let (chain, network, address, asset, amount) = bincode::deserialize(data)
            .map_err(|e| OtherError::new(format!("Failed to deserialize Withdraw: {}", e)))?;
        Ok(Self {
            chain,
            network,
            address,

            asset,
            amount,
        })
    }

    pub fn cbor_encode(&self) -> Vec<u8> {
        cbor::serialize(&self).unwrap()
    }

    pub fn cbor_decode(data: &[u8]) -> Result<Self, OtherError> {
        let withdrawal = cbor::deserialize(data).unwrap();
        Ok(withdrawal)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_bincode_decode_encode() {
        let withdrawal = Withdrawal {
            chain: Chain::Ethereum,
            network: 1,
            address: "0x0000001312312321313213".to_string(),
            asset: AssetId::from(2000u32),
            amount: AssetAmount::from(10000u64),
        };

        let encoded = withdrawal.encode();
        let decoded = Withdrawal::decode(&encoded).unwrap();
        assert_eq!(withdrawal, decoded);
    }

    #[test]
    fn test_cbor_decode_encode() {
        let withdrawal = Withdrawal {
            chain: Chain::Ethereum,
            network: 1,
            address: "0x0000001312312321313213".to_string(),
            asset: AssetId::from(2000u32),
            amount: AssetAmount::from(10000u64),
        };

        let encoded = withdrawal.cbor_encode();
        let decoded = Withdrawal::cbor_decode(&encoded).unwrap();
        assert_eq!(withdrawal, decoded);
    }
}
