use std::collections::{BTreeMap, BTreeSet};
use std::{iter, slice};
use amplify::confinement::{LargeVec, MediumBlob, SmallOrdMap, TinyOrdMap, TinyOrdSet};
use commit_verify::Conceal;
use rgb::validation::{AnchoredBundle, ConsignmentApi};
use rgb::{
    validation, AttachId, BundleId, ContractHistory, ContractId, Extension, Genesis, GraphSeal,
    OpId, OpRef, Operation, Schema, SchemaId, SecretSeal, SubSchema, Transition, TransitionBundle,
    WitnessAnchor,
};
use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize};
use super::{ContainerVer, ContentId, ContentSigs, Terminal};
use crate::accessors::BundleExt;
use crate::interface::{ContractSuppl, IfaceId, IfacePair};
use crate::resolvers::ResolveHeight;
use crate::LIB_NAME_RGB_STD;
pub type Transfer = Consignment<true>;
pub type Contract = Consignment<false>;
#[derive(Clone, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB_STD)]
#[cfg_attr(
    feature = "serde",
    derive(Serialize, Deserialize),
    serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Consignment<const TYPE: bool> {
    #[strict_type(skip, dumb = None)]
    #[cfg_attr(feature = "serde", serde(skip))]
    pub(super) validation_status: Option<validation::Status>,
    pub version: ContainerVer,
    pub transfer: bool,
    pub schema: SubSchema,
    pub ifaces: TinyOrdMap<IfaceId, IfacePair>,
    pub supplements: TinyOrdSet<ContractSuppl>,
    pub genesis: Genesis,
    pub terminals: SmallOrdMap<BundleId, Terminal>,
    pub bundles: LargeVec<AnchoredBundle>,
    pub extensions: LargeVec<Extension>,
    pub attachments: SmallOrdMap<AttachId, MediumBlob>,
    pub signatures: TinyOrdMap<ContentId, ContentSigs>,
}
impl<const TYPE: bool> StrictSerialize for Consignment<TYPE> {}
impl<const TYPE: bool> StrictDeserialize for Consignment<TYPE> {}
impl<const TYPE: bool> Consignment<TYPE> {
    pub fn new(schema: SubSchema, genesis: Genesis) -> Self {
        assert_eq!(schema.schema_id(), genesis.schema_id);
        Consignment {
            validation_status: None,
            version: ContainerVer::V1,
            transfer: TYPE,
            schema,
            ifaces: none!(),
            supplements: none!(),
            genesis,
            terminals: none!(),
            bundles: none!(),
            extensions: none!(),
            attachments: none!(),
            signatures: none!(),
        }
    }
    #[inline]
    pub fn schema_id(&self) -> SchemaId { self.schema.schema_id() }
    #[inline]
    pub fn root_schema_id(&self) -> Option<SchemaId> {
        self.schema.subset_of.as_ref().map(Schema::schema_id)
    }
    #[inline]
    pub fn contract_id(&self) -> ContractId { self.genesis.contract_id() }
    pub fn anchored_bundle(&self, bundle_id: BundleId) -> Option<&AnchoredBundle> {
        self.bundles
            .iter()
            .find(|anchored_bundle| anchored_bundle.bundle.bundle_id() == bundle_id)
    }
    pub fn validation_status(&self) -> Option<&validation::Status> {
        self.validation_status.as_ref()
    }
    pub fn into_validation_status(self) -> Option<validation::Status> { self.validation_status }
    pub fn update_history<R: ResolveHeight>(
        &self,
        history: Option<&ContractHistory>,
        resolver: &mut R,
    ) -> Result<ContractHistory, R::Error> {
        let mut history = history.cloned().unwrap_or_else(|| {
            ContractHistory::with(
                self.schema_id(),
                self.root_schema_id(),
                self.contract_id(),
                &self.genesis,
            )
        });
        let mut extension_idx = self
            .extensions
            .iter()
            .map(Extension::id)
            .zip(iter::repeat(false))
            .collect::<BTreeMap<_, _>>();
        let mut ordered_extensions = BTreeMap::new();
        for anchored_bundle in &self.bundles {
            for item in anchored_bundle.bundle.values() {
                if let Some(transition) = &item.transition {
                    let txid = anchored_bundle.anchor.txid;
                    let height = resolver.resolve_height(txid)?;
                    let ord_txid = WitnessAnchor::new(height, txid);
                    history.add_transition(transition, ord_txid);
                    for (id, used) in &mut extension_idx {
                        if *used {
                            continue;
                        }
                        for input in &transition.inputs {
                            if input.prev_out.op == *id {
                                *used = true;
                                if let Some(ord) = ordered_extensions.get_mut(id) {
                                    if *ord > ord_txid {
                                        *ord = ord_txid;
                                    }
                                } else {
                                    ordered_extensions.insert(*id, ord_txid);
                                }
                            }
                        }
                    }
                }
            }
        }
        for extension in &self.extensions {
            if let Some(ord_txid) = ordered_extensions.get(&extension.id()) {
                history.add_extension(extension, *ord_txid);
            }
        }
        Ok(history)
    }
    pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: GraphSeal) {
        for anchored_bundle in &mut self.bundles {
            if anchored_bundle.bundle.bundle_id() == bundle_id {
                anchored_bundle.bundle.reveal_seal(revealed);
            }
        }
    }
    pub fn into_contract(self) -> Contract {
        Contract {
            validation_status: self.validation_status,
            version: self.version,
            transfer: false,
            schema: self.schema,
            ifaces: self.ifaces,
            supplements: self.supplements,
            genesis: self.genesis,
            terminals: self.terminals,
            bundles: self.bundles,
            extensions: self.extensions,
            attachments: self.attachments,
            signatures: self.signatures,
        }
    }
}
impl<const TYPE: bool> ConsignmentApi for Consignment<TYPE> {
    type BundleIter<'container>
    = slice::Iter<'container, AnchoredBundle> where Self: 'container;
    fn schema(&self) -> &SubSchema { &self.schema }
    fn operation(&self, opid: OpId) -> Option<OpRef> {
        if opid == self.genesis.id() {
            return Some(OpRef::Genesis(&self.genesis));
        }
        self.transition(opid)
            .map(OpRef::from)
            .or_else(|| self.extension(opid).map(OpRef::from))
    }
    fn genesis(&self) -> &Genesis { &self.genesis }
    fn transition(&self, opid: OpId) -> Option<&Transition> {
        for anchored_bundle in &self.bundles {
            for (id, item) in anchored_bundle.bundle.iter() {
                if *id == opid {
                    return item.transition.as_ref();
                }
            }
        }
        None
    }
    fn extension(&self, opid: OpId) -> Option<&Extension> {
        self.extensions
            .iter()
            .find(|&extension| extension.id() == opid)
    }
    fn terminals(&self) -> BTreeSet<(BundleId, SecretSeal)> {
        self.terminals
            .iter()
            .flat_map(|(bundle_id, terminal)| {
                terminal
                    .seals
                    .iter()
                    .map(|seal| (*bundle_id, seal.conceal()))
            })
            .collect()
    }
    fn anchored_bundles(&self) -> Self::BundleIter<'_> { self.bundles.iter() }
    fn bundle_by_id(&self, bundle_id: BundleId) -> Option<&TransitionBundle> {
        self.anchored_bundle(bundle_id).map(|ab| &ab.bundle)
    }
    fn op_ids_except(&self, ids: &BTreeSet<OpId>) -> BTreeSet<OpId> {
        let mut exceptions = BTreeSet::new();
        for anchored_bundle in &self.bundles {
            for item in anchored_bundle.bundle.values() {
                if let Some(id) = item.transition.as_ref().map(Transition::id) {
                    if !ids.contains(&id) {
                        exceptions.insert(id);
                    }
                }
            }
        }
        exceptions
    }
    fn has_operation(&self, opid: OpId) -> bool { self.operation(opid).is_some() }
    fn known_transitions_by_bundle_id(&self, bundle_id: BundleId) -> Option<Vec<&Transition>> {
        self.bundle_by_id(bundle_id).map(|bundle| {
            bundle
                .values()
                .filter_map(|item| item.transition.as_ref())
                .collect()
        })
    }
}