use std::fmt;
use std::ops;

use ciborium::tag::Captured;
use ex3_crypto::sha256;
use ex3_payload_derive::Ex3Payload;
use ex3_serde::bincode::{deserialize, serialize};
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, Nonce, PublicKey, Version, WalletRegisterId};

pub type TransactionHash = [u8; 32];
pub type r#Type = u32;

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Transaction {
    pub r#type: r#Type,
    pub version: Version,
    pub from: WalletRegisterId,
    /// Nonce is a number that is used only once for every transaction per wallet.
    /// if the transaction type is [Deposit, WALLET_REGISTER, RESET_MAIN_SECRET], this field is 0
    /// Otherwise, this field is start from 1
    pub nonce: Nonce,
    pub payload: ByteBuf,
    /// Signature is the signature of the transaction.
    pub signature: ByteBuf,
}

impl Transaction {
    pub fn hash(&self) -> TransactionHash {
        let bytes = bincode::serialize(&self).unwrap();
        sha256!(&bytes)
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(transparent)]
pub struct EncodedTransaction(pub ByteBuf);

impl From<Transaction> for EncodedTransaction {
    fn from(tx: Transaction) -> Self {
        let bytes = bincode::serialize(&tx).unwrap();
        EncodedTransaction(ByteBuf::from(bytes))
    }
}

impl From<&Transaction> for EncodedTransaction {
    fn from(tx: &Transaction) -> Self {
        let bytes = bincode::serialize(tx).unwrap();
        EncodedTransaction(ByteBuf::from(bytes))
    }
}

impl From<EncodedTransaction> for Transaction {
    fn from(tx: EncodedTransaction) -> Self {
        bincode::deserialize(tx.0.as_ref()).unwrap()
    }
}

impl From<&EncodedTransaction> for Transaction {
    fn from(tx: &EncodedTransaction) -> Self {
        bincode::deserialize(tx.0.as_ref()).unwrap()
    }
}

impl ops::Deref for EncodedTransaction {
    type Target = ByteBuf;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/// Transaction type
/// | type                    | code              | remark                               |
/// | ----------------------- | ----------------- | ------------------------------------ |
/// | WALLET_REGISTER         | 100u32            | Wallet register.                     |
/// | DEPOSIT                 | 200u32            | Deposit transaction.                 |
/// | WITHDRAWAL              | 201u32            | Withdraw transaction.                |
/// | TRANSFER                | 300u32            | Transfer transaction.                |
/// | RESET_MAIN_SECRET       | 400u32            | Create main secret transaction.      |
/// | CREATE_API_SECRET       | 401u32            | Create api secret transaction.       |
/// | DESTROY_API_SECRET      | 402u32            | Destroy api secret transaction.      |
/// | CREATE_SPOT_ORDER       | 500u32            | Submit spot order.                   |
/// | CANCEL_SPOT_ORDER       | 501u32            | Cancel spot order.                   |
/// | ADD_AMM_V2_LIQUIDITY    | 600u32            | Add AMM-V2 liquidity.                |
/// | REMOVE_AMM_V2_LIQUIDITY | 601u32            | Remove AMM-V2 liquidity.             |
#[allow(dead_code)]
pub mod transaction_type {
    pub const WALLET_REGISTER: u32 = 100u32;
    pub const DEPOSIT: u32 = 200u32;
    pub const WITHDRAWAL: u32 = 201u32;
    pub const TRANSFER: u32 = 300u32;
    pub const RESET_MAIN_SECRET: u32 = 400u32;
    pub const CREATE_API_SECRET: u32 = 401u32;
    pub const DESTROY_API_SECRET: u32 = 402u32;
    pub const CREATE_SPOT_ORDER: u32 = 500u32;
    pub const CANCEL_SPOT_ORDER: u32 = 501u32;
    pub const ADD_AMM_V2_LIQUIDITY: u32 = 600u32;
    pub const REMOVE_AMM_V2_LIQUIDITY: u32 = 601u32;
}

#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
pub struct ResetMainSecretRequest {
    pub encrypted_pri_key: ByteBuf,
    pub l2_pub_key: PublicKey,
}

impl ResetMainSecretRequest {
    pub fn encode(&self) -> Vec<u8> {
        serialize(&(&self.encrypted_pri_key, &self.l2_pub_key)).unwrap()
    }

    pub fn decode(bytes: &[u8]) -> Result<Self, String> {
        let (encrypted_pri_key, l2_pub_key) = deserialize(bytes)
            .map_err(|e| format!("Failed to deserialize ResetMainSecretRequest: {}", e))?;

        Ok(Self {
            encrypted_pri_key,
            l2_pub_key,
        })
    }
}

#[derive(Clone, Debug, Eq, PartialEq, Ex3Payload)]
pub struct Transfer {
    #[index(0)]
    #[alias_of_big_uint]
    pub to: WalletRegisterId,
    #[index(1)]
    #[alias_of_big_uint]
    pub asset: AssetId,
    #[index(2)]
    #[alias_of_big_uint]
    pub amount: AssetAmount,
}

#[derive(Clone, Debug, Eq, PartialEq, Ex3Payload)]
pub struct BatchTransfer {
    #[index(0)]
    pub items: Vec<Transfer>,
}

impl Transfer {
    pub fn encode(&self) -> Vec<u8> {
        serialize(&(&self.to, &self.asset, &self.amount)).unwrap()
    }

    pub fn decode(bytes: &[u8]) -> Result<Self, String> {
        let (to, asset, amount) =
            deserialize(bytes).map_err(|e| format!("Failed to deserialize Transfer: {}", e))?;

        Ok(Self { to, asset, amount })
    }

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

    pub fn cbor_decode(bytes: &[u8]) -> Result<Self, String> {
        let transfer = cbor::deserialize(bytes)
            .map_err(|e| format!("Failed to deserialize Transfer: {}", e))?;

        Ok(transfer)
    }
}
