use std::fmt;

use ciborium::tag::Captured;
use ex3_node_error::OtherError;
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 ex3_payload_derive::Ex3Payload;

use crate::{AssetAmount, AssetId, BlockHeight};

#[derive(Debug, Clone, PartialEq, Eq, Hash, Ex3Payload)]
pub struct DepositIdentifier {
    #[index(0)]
    #[alias_of_big_uint]
    pub asset_id: AssetId,
    /// The block height
    /// Any chain does not have a block height? we use the timestamp instead? (TBD)
    #[index(1)]
    #[alias_of_big_uint]
    pub block_height: BlockHeight,
    /// The block chain transaction id
    /// For UTXO, it is the transaction id + tx index
    #[index(2)]
    pub tx_id: Option<String>,
}

impl DepositIdentifier {
    pub fn encode(&self) -> Vec<u8> {
        bincode::serialize(&(&self.asset_id, &self.block_height, &self.tx_id)).unwrap()
    }
    pub fn decode(bytes: &[u8]) -> Result<Self, OtherError> {
        let (asset_id, block_height, tx_id) = bincode::deserialize(bytes)
            .map_err(|e| OtherError::new(format!("deserialize error: {}", e)))?;
        Ok(DepositIdentifier {
            asset_id,
            block_height,
            tx_id,
        })
    }
}

/// Deposit
#[derive(Debug, Clone, Eq, PartialEq, Ex3Payload)]
pub struct Deposit {
    #[index(0)]
    pub identifier: DepositIdentifier,
    #[index(1)]
    #[alias_of_big_uint]
    pub amount: AssetAmount,
}

impl Deposit {
    pub fn encode(&self) -> Vec<u8> {
        bincode::serialize(&(&self.identifier, &self.amount)).unwrap()
    }
    pub fn decode(bytes: &[u8]) -> Result<Self, OtherError> {
        let (identifier, amount) = bincode::deserialize(bytes)
            .map_err(|e| OtherError::new(format!("deserialize error: {}", e)))?;
        Ok(Deposit { identifier, amount })
    }

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

    pub fn cbor_decode(bytes: &[u8]) -> Result<Self, OtherError> {
        let deposit = cbor::deserialize(bytes)
            .map_err(|e| OtherError::new(format!("deserialize error: {}", e)))?;
        Ok(deposit)
    }
}
