use std::collections::BTreeMap;
use std::ops::{Add, Sub};
use std::sync::Arc;

use ex3_balance_vault_public_types::{
    BalanceDataIntegrityShardingSubReport, CandidBalanceShardingSubReport,
    CandidWithdrawalShardingSubReport,
};
use ex3_blockchain_public_types::{BlockHead, CandidConsensusReport};
use ex3_canister_types::report::DataIntegrityShardingSubReport;
use ex3_secret_vault_public_types::CandidSecretUpdateTxShardingSubReport;
use ex3_serde::bincode::{deserialize, serialize};
use ex3_wallet_registry_public_types::CandidWalletRegisterTxShardingSubReport;
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;

use ex3_node_error::OtherError;

use crate::transaction::EncodedTransaction;
use crate::{AssetAmount, AssetId, BlockHeight, MerkleNode, VaultSeqId, WalletRegisterId};

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BlockHeadExt {
    pub height: BlockHeight,
    pub validate_on_chain: bool,
    pub sharding_reported: bool,
    pub has_local_body: bool,
}

impl BlockHeadExt {
    pub fn encode(&self) -> Vec<u8> {
        serialize(&(
            &self.validate_on_chain,
            &self.sharding_reported,
            &self.has_local_body,
            &self.height,
        ))
        .unwrap()
    }

    pub fn decode(bytes: &[u8]) -> Result<Self, OtherError> {
        let (validate_on_chain, has_been_reported, has_local_body, height) =
            deserialize(bytes).map_err(|e| OtherError::new(format!("{:?}", e)))?;
        Ok(Self {
            validate_on_chain,
            sharding_reported: has_been_reported,
            has_local_body,
            height,
        })
    }
}

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

    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
        Self::decode(bytes)
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Block {
    pub head: BlockHead,
    pub body: Option<BlockBody>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockBody {
    pub txs: Vec<EncodedTransaction>,
    pub balances_changed: Vec<(WalletRegisterId, Vec<(AssetId, BalanceChanged)>)>,
    pub data_integrity: Vec<MerkleNode>,
}

impl BlockBody {
    pub fn encode(&self) -> Vec<u8> {
        serialize(&(&self.txs, &self.balances_changed)).unwrap()
    }
}

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

    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
        let (txs, balances_changed, data_integrity) =
            deserialize(bytes).map_err(|e| OtherError::new(format!("{:?}", e)))?;
        Ok(Self {
            txs,
            balances_changed,
            data_integrity,
        })
    }
}

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

impl From<BlockBody> for EncodedBlockBody {
    fn from(body: BlockBody) -> Self {
        let bytes = serialize(&body.encode()).unwrap();
        EncodedBlockBody(ByteBuf::from(bytes))
    }
}

impl From<EncodedBlockBody> for BlockBody {
    fn from(encoded_body: EncodedBlockBody) -> Self {
        encoded_body.0.as_slice().try_into().unwrap()
    }
}

#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub enum BalanceChanged {
    Increase(AssetAmount),
    Decrease(AssetAmount),
}

impl BalanceChanged {
    pub fn merge(&self, other: &Self) -> Self {
        match (self, other) {
            (BalanceChanged::Increase(a), BalanceChanged::Increase(b)) => {
                BalanceChanged::Increase(a.add(b))
            }
            (BalanceChanged::Decrease(a), BalanceChanged::Decrease(b)) => {
                BalanceChanged::Decrease(a.add(b))
            }
            (BalanceChanged::Increase(a), BalanceChanged::Decrease(b)) => {
                if a >= b {
                    BalanceChanged::Increase(a.sub(b))
                } else {
                    BalanceChanged::Decrease(b.sub(a))
                }
            }
            (BalanceChanged::Decrease(a), BalanceChanged::Increase(b)) => {
                if a >= b {
                    BalanceChanged::Decrease(a.sub(b))
                } else {
                    BalanceChanged::Increase(b.sub(a))
                }
            }
        }
    }
}

type WalletRegistrySeqId = VaultSeqId;
type BalanceVaultSeqId = VaultSeqId;
type SecretVaultSeqId = VaultSeqId;

#[derive(Debug, Clone)]
pub struct BlockWithShardingReports {
    pub block: Arc<Block>,
    pub consensus_report: CandidConsensusReport,
    pub wallet_register_tx_reports:
        BTreeMap<WalletRegistrySeqId, Vec<CandidWalletRegisterTxShardingSubReport>>,
    pub wallet_registry_data_integrity_reports:
        BTreeMap<WalletRegistrySeqId, DataIntegrityShardingSubReport>,
    pub secret_update_tx_reports:
        BTreeMap<SecretVaultSeqId, Vec<CandidSecretUpdateTxShardingSubReport>>,
    pub secret_vault_data_integrity_reports:
        BTreeMap<SecretVaultSeqId, DataIntegrityShardingSubReport>,
    pub balance_changed_reports: BTreeMap<BalanceVaultSeqId, Vec<CandidBalanceShardingSubReport>>,
    pub withdrawal_reports: BTreeMap<BalanceVaultSeqId, Vec<CandidWithdrawalShardingSubReport>>,
    pub balance_vault_data_integrity_reports:
        BTreeMap<BalanceVaultSeqId, BalanceDataIntegrityShardingSubReport>,
}

impl BlockWithShardingReports {
    pub fn get_block(&self) -> Arc<Block> {
        self.block.clone()
    }

    pub fn get_consensus_report(&self) -> CandidConsensusReport {
        self.consensus_report.clone()
    }

    pub fn get_wallet_register_tx_reports(
        &self,
        wallet_vault_seq_id: &WalletRegistrySeqId,
    ) -> Vec<CandidWalletRegisterTxShardingSubReport> {
        self.wallet_register_tx_reports
            .get(wallet_vault_seq_id)
            .expect("Wallet register report should exist")
            .clone()
    }

    pub fn get_balance_changed_reports(
        &self,
        balance_vault_seq_id: &BalanceVaultSeqId,
    ) -> Vec<CandidBalanceShardingSubReport> {
        self.balance_changed_reports
            .get(balance_vault_seq_id)
            .expect("Balance changed report should exist")
            .clone()
    }

    pub fn get_withdrawal_reports(
        &self,
        balance_vault_seq_id: &BalanceVaultSeqId,
    ) -> Vec<CandidWithdrawalShardingSubReport> {
        self.withdrawal_reports
            .get(balance_vault_seq_id)
            .expect("Withdrawal report should exist")
            .clone()
    }

    pub fn get_secret_update_tx_reports(
        &self,
        secret_vault_seq_id: &SecretVaultSeqId,
    ) -> Vec<CandidSecretUpdateTxShardingSubReport> {
        self.secret_update_tx_reports
            .get(secret_vault_seq_id)
            .expect("Secret update tx report should exist")
            .clone()
    }

    pub fn get_data_integrity_reports_of_balance_vault(
        &self,
        balance_vault_seq_id: &BalanceVaultSeqId,
    ) -> BalanceDataIntegrityShardingSubReport {
        self.balance_vault_data_integrity_reports
            .get(balance_vault_seq_id)
            .expect("Data integrity report should exist")
            .clone()
    }

    pub fn get_data_integrity_reports_of_secret_vault(
        &self,
        secret_vault_seq_id: &SecretVaultSeqId,
    ) -> DataIntegrityShardingSubReport {
        self.secret_vault_data_integrity_reports
            .get(secret_vault_seq_id)
            .expect("Data integrity report should exist")
            .clone()
    }

    pub fn get_data_integrity_reports_of_wallet_registry(
        &self,
        wallet_registry_seq_id: &WalletRegistrySeqId,
    ) -> DataIntegrityShardingSubReport {
        self.wallet_registry_data_integrity_reports
            .get(wallet_registry_seq_id)
            .expect("Data integrity report should exist")
            .clone()
    }
}
