use super::{
    Attributes, Generic, Identifier, Input, Output, Parameter, SharingError, SharingResult,
    TreatmentDesign,
};
use melodium_common::descriptor::{
    Collection, Entry as CommonEntry, Identifier as CommonIdentifier, Treatment as CommonTreatment,
};
use melodium_engine::{descriptor::Treatment as DesignedTreatment, LogicError};
use serde::{Deserialize, Serialize};
use std::{
    collections::{BTreeMap, HashMap},
    sync::Arc,
};

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "webassembly", derive(tsify::Tsify))]
#[cfg_attr(feature = "webassembly", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Treatment {
    pub identifier: Identifier,
    pub documentation: String,
    pub generics: BTreeMap<String, Generic>,
    pub parameters: BTreeMap<String, Parameter>,
    pub implementation_kind: TreatmentImplementationKind,
    pub inputs: BTreeMap<String, Input>,
    pub outputs: BTreeMap<String, Output>,
    pub models: BTreeMap<String, Identifier>,
    pub contexts: BTreeMap<String, Identifier>,
    pub attributes: Attributes,
}

impl Treatment {
    pub fn make_descriptor(
        &self,
        collection: &Collection,
    ) -> SharingResult<Arc<DesignedTreatment>> {
        let identifier = if let Ok(identifier) = (&self.identifier).try_into() {
            identifier
        } else {
            return SharingResult::new_failure(SharingError::invalid_identifier(
                11,
                self.identifier.clone(),
            ));
        };

        if self.implementation_kind.is_compiled() {
            return SharingResult::new_failure(SharingError::compiled_treatment(12, identifier));
        }

        let mut result = SharingResult::new_success(());
        let mut descriptor = DesignedTreatment::new(identifier.clone());

        descriptor.set_documentation(&self.documentation);

        for (name, attribute) in &self.attributes.0 {
            descriptor.add_attribute(name.clone(), attribute.clone());
        }

        for (_, generic) in &self.generics {
            descriptor.add_generic(generic.into());
        }

        for (name, model) in &self.models {
            let model_identifier: CommonIdentifier = if let Ok(identifier) = model.try_into() {
                identifier
            } else {
                return SharingResult::new_failure(SharingError::invalid_identifier(
                    12,
                    self.identifier.clone(),
                ));
            };

            if let Some(CommonEntry::Model(model)) = collection.get(&(&model_identifier).into()) {
                descriptor.add_model(name, model);
            } else {
                result = result.and(SharingResult::new_failure(
                    LogicError::unexisting_model(
                        236,
                        identifier.clone(),
                        model_identifier.into(),
                        None,
                    )
                    .into(),
                ));
            }
        }

        for (_, context) in &self.contexts {
            let context_identifier: CommonIdentifier = if let Ok(identifier) = context.try_into() {
                identifier
            } else {
                return SharingResult::new_failure(SharingError::invalid_identifier(
                    13,
                    self.identifier.clone(),
                ));
            };

            if let Some(CommonEntry::Context(context)) =
                collection.get(&(&context_identifier).into())
            {
                descriptor.add_context(context);
            } else {
                result = result.and(SharingResult::new_failure(
                    LogicError::unexisting_context(
                        237,
                        identifier.clone(),
                        context_identifier.into(),
                        None,
                    )
                    .into(),
                ));
            }
        }

        for (_, param) in &self.parameters {
            if let Some(parameter) =
                result.merge_degrade_failure(param.to_parameter(collection, &identifier))
            {
                descriptor.add_parameter(parameter);
            }
        }

        for (_, input) in &self.inputs {
            if let Some(input) =
                result.merge_degrade_failure(input.to_input(collection, &identifier))
            {
                descriptor.add_input(input);
            }
        }

        for (_, output) in &self.outputs {
            if let Some(output) =
                result.merge_degrade_failure(output.to_output(collection, &identifier))
            {
                descriptor.add_output(output);
            }
        }

        result.and(SharingResult::new_success(descriptor.commit()))
    }

    pub fn make_design(&self, collection: &Arc<Collection>) -> SharingResult<()> {
        let identifier = if let Ok(identifier) = (&self.identifier).try_into() {
            identifier
        } else {
            return SharingResult::new_failure(SharingError::invalid_identifier(
                14,
                self.identifier.clone(),
            ));
        };

        let design = if let TreatmentImplementationKind::Designed(Some(design)) =
            &self.implementation_kind
        {
            design
        } else {
            return SharingResult::new_failure(SharingError::no_treatment_design_available(
                15, identifier,
            ));
        };

        let descriptor = if let Some(CommonEntry::Treatment(treatment)) =
            collection.get(&(&identifier).into())
        {
            if let Ok(treatment) = treatment.clone().downcast_arc::<DesignedTreatment>() {
                treatment
            } else {
                return SharingResult::new_failure(
                    LogicError::unexisting_treatment(
                        238,
                        identifier.clone(),
                        identifier.into(),
                        None,
                    )
                    .into(),
                );
            }
        } else {
            return SharingResult::new_failure(
                LogicError::unexisting_treatment(239, identifier.clone(), identifier.into(), None)
                    .into(),
            );
        };

        let designer = match descriptor.designer(Arc::clone(collection), None) {
            melodium_common::descriptor::Status::Success { success, errors: _ } => success,
            melodium_common::descriptor::Status::Failure { failure, errors: _ } => {
                return SharingResult::new_failure(failure.into())
            }
        };

        design
            .make_design(collection, &descriptor)
            .and_then(|design| {
                SharingResult::from(designer.write().unwrap().import_design(
                    &design,
                    &HashMap::new(),
                    None,
                ))
            })
            .and_then(|()| SharingResult::from(descriptor.commit_design()))
    }
}

impl From<&Arc<dyn CommonTreatment>> for Treatment {
    fn from(value: &Arc<dyn CommonTreatment>) -> Self {
        Self {
            identifier: Identifier::from(value.identifier()),
            documentation: value.documentation().to_string(),
            generics: value
                .generics()
                .iter()
                .map(|g| (g.name.clone(), g.into()))
                .collect(),
            parameters: value
                .parameters()
                .iter()
                .map(|(name, param)| (name.clone(), Parameter::from(param)))
                .collect(),
            implementation_kind: match value.build_mode() {
                melodium_common::descriptor::TreatmentBuildMode::Compiled(_, _)
                | melodium_common::descriptor::TreatmentBuildMode::Source(_) => {
                    TreatmentImplementationKind::Compiled
                }
                melodium_common::descriptor::TreatmentBuildMode::Designed() => {
                    TreatmentImplementationKind::Designed(
                        value
                            .clone()
                            .downcast_arc::<DesignedTreatment>()
                            .unwrap()
                            .design()
                            .success()
                            .map(|design| design.as_ref().into()),
                    )
                }
            },
            inputs: value
                .inputs()
                .iter()
                .map(|(name, input)| (name.clone(), Input::from(input)))
                .collect(),
            outputs: value
                .outputs()
                .iter()
                .map(|(name, output)| (name.clone(), Output::from(output)))
                .collect(),
            models: value
                .models()
                .iter()
                .map(|(name, model)| (name.clone(), Identifier::from(model.identifier())))
                .collect(),
            contexts: value
                .contexts()
                .iter()
                .map(|(name, context)| (name.clone(), Identifier::from(context.identifier())))
                .collect(),
            attributes: value.attributes().into(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "webassembly", derive(tsify::Tsify))]
#[cfg_attr(feature = "webassembly", tsify(into_wasm_abi, from_wasm_abi))]
pub enum TreatmentImplementationKind {
    Compiled,
    Designed(Option<TreatmentDesign>),
}

impl TreatmentImplementationKind {
    pub fn is_compiled(&self) -> bool {
        match self {
            TreatmentImplementationKind::Compiled => true,
            _ => false,
        }
    }
}
