use amplify::num::u5;
use amplify::ByteArray;
use bitcoin::hashes::Hash;
use bitcoin::psbt::Psbt;
use bitcoin::secp256k1::SECP256K1;
use bitcoin::taproot::{TapTree, TaprootBuilder, TaprootBuilderError};
use bitcoin::ScriptBuf;
use bp::dbc::tapret::{TapretCommitment, TapretPathProof, TapretProof};
use bp::dbc::Proof;
use bp::seals::txout::CloseMethod;
use bp::TapScript;
use commit_verify::{mpc, CommitVerify, CommitmentId, TryCommitVerify};
use rgb::Anchor;
use crate::psbt::lnpbp4::OutputLnpbp4;
use crate::psbt::opret::OutputOpret;
use crate::psbt::tapret::{OutputTapret, TapretKeyError};
use crate::psbt::{Lnpbp4PsbtError, OpretKeyError, PSBT_OUT_LNPBP4_MIN_TREE_DEPTH};
#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum DbcPsbtError {
    TapTreeNonEmpty,
    NoInternalKey,
    NoHostOutput,
    MultipleCommitmentHosts,
    MethodUnsupported(CloseMethod),
    #[from]
    #[display(inner)]
    Mpc(mpc::Error),
    #[from]
    #[display(inner)]
    Lnpbp4Psbt(Lnpbp4PsbtError),
    #[from]
    #[display(inner)]
    TapretKey(TapretKeyError),
    #[from]
    #[display(inner)]
    OpretKey(OpretKeyError),
    #[from]
    #[display(inner)]
    TaprootBuilder(TaprootBuilderError),
}
pub trait PsbtDbc {
    fn dbc_conclude(
        &mut self,
        method: CloseMethod,
    ) -> Result<Anchor<mpc::MerkleBlock>, DbcPsbtError>;
}
impl PsbtDbc for Psbt {
    fn dbc_conclude(
        &mut self,
        method: CloseMethod,
    ) -> Result<Anchor<mpc::MerkleBlock>, DbcPsbtError> {
        if self
            .outputs
            .iter()
            .filter(|output| output.is_tapret_host() | output.is_opret_host())
            .count() >
            1
        {
            return Err(DbcPsbtError::MultipleCommitmentHosts);
        }
        let (vout, output) = self
            .outputs
            .iter_mut()
            .enumerate()
            .find(|(_, output)| {
                (output.is_tapret_host() && method == CloseMethod::TapretFirst) |
                    (output.is_opret_host() && method == CloseMethod::OpretFirst)
            })
            .ok_or(DbcPsbtError::NoHostOutput)?;
        let txout = &mut self.unsigned_tx.output[vout];
        let messages = output.lnpbp4_message_map()?;
        let min_depth = u5::with(
            output
                .lnpbp4_min_tree_depth()
                .unwrap_or(PSBT_OUT_LNPBP4_MIN_TREE_DEPTH),
        );
        let source = mpc::MultiSource {
            min_depth,
            messages,
        };
        let merkle_tree = mpc::MerkleTree::try_commit(&source)?;
        let entropy = merkle_tree.entropy();
        output.set_lnpbp4_entropy(entropy)?;
        let commitment = merkle_tree.commitment_id();
        let proof = if method == CloseMethod::TapretFirst {
            if output.tap_tree.is_some() {
                return Err(DbcPsbtError::TapTreeNonEmpty);
            }
            let tapret_commitment = &TapretCommitment::with(commitment, 0);
            let script_commitment =
                ScriptBuf::from_bytes(TapScript::commit(tapret_commitment).to_vec());
            let builder = TaprootBuilder::with_capacity(1).add_leaf(0, script_commitment)?;
            let tap_tree = TapTree::try_from(builder.clone()).expect("builder is complete");
            let internal_pk = output.tap_internal_key.ok_or(DbcPsbtError::NoInternalKey)?;
            let tapret_proof = TapretProof {
                path_proof: TapretPathProof::root(tapret_commitment.nonce),
                internal_pk: internal_pk.into(),
            };
            output.tap_tree = Some(tap_tree);
            let spent_info = builder
                .finalize(SECP256K1, internal_pk)
                .expect("complete tree");
            let merkle_root = spent_info.merkle_root().expect("script tree present");
            output.set_tapret_commitment(commitment, &tapret_proof.path_proof)?;
            txout.script_pubkey = ScriptBuf::new_v1_p2tr(SECP256K1, internal_pk, Some(merkle_root));
            Proof::TapretFirst(tapret_proof)
        } else if method == CloseMethod::OpretFirst {
            output.set_opret_commitment(commitment)?;
            txout.script_pubkey = ScriptBuf::new_op_return(&commitment.to_byte_array());
            Proof::OpretFirst
        } else {
            return Err(DbcPsbtError::MethodUnsupported(method));
        };
        let anchor = Anchor {
            txid: self.unsigned_tx.txid().to_byte_array().into(),
            mpc_proof: mpc::MerkleBlock::from(merkle_tree),
            dbc_proof: proof,
        };
        Ok(anchor)
    }
}