use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::hash_map::{Entry, IterMut};
use std::collections::HashMap;
use std::fmt;
use std::hash::Hash;

#[cfg(feature = "pybindings")]
use pyo3::prelude::*;

#[cfg(feature = "pybindings")]
use pyo3::types::PyType;

#[cfg(feature = "jsbindings")]
use tsify_next::Tsify;

#[cfg(feature = "jsbindings")]
use wasm_bindgen::prelude::*;

#[cfg(feature = "jsbindings")]
use strum::IntoEnumIterator;

#[cfg(feature = "jsbindings")]
use strum_macros::EnumIter;

#[derive(Deserialize, Serialize, JsonSchema, Hash, Eq, PartialEq, Clone, Debug)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(
    feature = "jsbindings",
    derive(Tsify, EnumIter),
    tsify(into_wasm_abi, from_wasm_abi)
)]
#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int, frozen, hash))]
pub enum LifeCycleModule {
    A0,
    A1A3,
    A4,
    A5,
    B1,
    B2,
    B3,
    B4,
    B5,
    B6,
    B7,
    B8,
    C1,
    C2,
    C3,
    C4,
    D,
}

#[cfg(feature = "jsbindings")]
#[allow(non_snake_case)]
#[wasm_bindgen]
pub fn lifeCycleModules() -> Vec<LifeCycleModule> {
    LifeCycleModule::iter().collect()
}

impl fmt::Display for LifeCycleModule {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            LifeCycleModule::A0 => write!(f, "A0"),
            LifeCycleModule::A1A3 => write!(f, "A1A3"),
            LifeCycleModule::A4 => write!(f, "A4"),
            LifeCycleModule::A5 => write!(f, "A5"),
            LifeCycleModule::B1 => write!(f, "B1"),
            LifeCycleModule::B2 => write!(f, "B2"),
            LifeCycleModule::B3 => write!(f, "B3"),
            LifeCycleModule::B4 => write!(f, "B4"),
            LifeCycleModule::B5 => write!(f, "B5"),
            LifeCycleModule::B6 => write!(f, "B6"),
            LifeCycleModule::B7 => write!(f, "B7"),
            LifeCycleModule::B8 => write!(f, "B8"),
            LifeCycleModule::C1 => write!(f, "C1"),
            LifeCycleModule::C2 => write!(f, "C2"),
            LifeCycleModule::C3 => write!(f, "C3"),
            LifeCycleModule::C4 => write!(f, "C4"),
            LifeCycleModule::D => write!(f, "D"),
        }
    }
}

impl TryFrom<&str> for LifeCycleModule {
    type Error = String;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        match value.to_lowercase().as_str() {
            "a0" => Ok(LifeCycleModule::A0),
            "a1to3" => Ok(LifeCycleModule::A1A3),
            "a1a3" => Ok(LifeCycleModule::A1A3),
            "a1-3" => Ok(LifeCycleModule::A1A3),
            "a4" => Ok(LifeCycleModule::A4),
            "a5" => Ok(LifeCycleModule::A5),
            "b1" => Ok(LifeCycleModule::B1),
            "b2" => Ok(LifeCycleModule::B2),
            "b3" => Ok(LifeCycleModule::B3),
            "b4" => Ok(LifeCycleModule::B4),
            "b5" => Ok(LifeCycleModule::B5),
            "b6" => Ok(LifeCycleModule::B6),
            "b7" => Ok(LifeCycleModule::B7),
            "b8" => Ok(LifeCycleModule::B8),
            "c1" => Ok(LifeCycleModule::C1),
            "c2" => Ok(LifeCycleModule::C2),
            "c3" => Ok(LifeCycleModule::C3),
            "c4" => Ok(LifeCycleModule::C4),
            "d" => Ok(LifeCycleModule::D),
            value => Err(format!("Unknown lifecycle stage: '{}'", value)),
        }
    }
}

#[derive(Deserialize, Serialize, JsonSchema, Hash, Eq, PartialEq, Clone, Debug)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(
    feature = "jsbindings",
    derive(Tsify, EnumIter),
    tsify(into_wasm_abi, from_wasm_abi)
)]
#[cfg_attr(feature = "pybindings", pyclass(eq, eq_int, frozen, hash))]
pub enum ImpactCategoryKey {
    GWP,
    #[allow(non_camel_case_types)]
    GWP_FOS,
    #[allow(non_camel_case_types)]
    GWP_BIO,
    #[allow(non_camel_case_types)]
    GWP_LUL,
    ODP,
    AP,
    EP,
    #[allow(non_camel_case_types)]
    EP_FW,
    #[allow(non_camel_case_types)]
    EP_MAR,
    #[allow(non_camel_case_types)]
    EP_TER,
    POCP,
    ADPE,
    ADPF,
    PENRE,
    PERE,
    PERM,
    PERT,
    PENRT,
    PENRM,
    SM,
    PM,
    WDP,
    IRP,
    #[allow(non_camel_case_types)]
    ETP_FW,
    #[allow(non_camel_case_types)]
    HTP_C,
    #[allow(non_camel_case_types)]
    HTP_NC,
    SQP,
    RSF,
    NRSF,
    FW,
    HWD,
    NHWD,
    RWD,
    CRU,
    MRF,
    MER,
    EEE,
    EET,
}

#[cfg(feature = "jsbindings")]
#[allow(non_snake_case)]
#[wasm_bindgen]
pub fn impactCategories() -> Vec<ImpactCategoryKey> {
    ImpactCategoryKey::iter().collect()
}

impl fmt::Display for ImpactCategoryKey {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ImpactCategoryKey::GWP => write!(f, "GWP"),
            ImpactCategoryKey::ODP => write!(f, "ODP"),
            ImpactCategoryKey::AP => write!(f, "AP"),
            ImpactCategoryKey::EP => write!(f, "EP"),
            ImpactCategoryKey::POCP => write!(f, "POCP"),
            ImpactCategoryKey::ADPE => write!(f, "ADPE"),
            ImpactCategoryKey::ADPF => write!(f, "ADPF"),
            ImpactCategoryKey::PERE => write!(f, "PERE"),
            ImpactCategoryKey::PERM => write!(f, "PERM"),
            ImpactCategoryKey::PERT => write!(f, "PERT"),
            ImpactCategoryKey::PENRT => write!(f, "PENRT"),
            ImpactCategoryKey::PENRM => write!(f, "PENRM"),
            ImpactCategoryKey::SM => write!(f, "SM"),
            ImpactCategoryKey::RSF => write!(f, "RSF"),
            ImpactCategoryKey::NRSF => write!(f, "NRSF"),
            ImpactCategoryKey::FW => write!(f, "FW"),
            ImpactCategoryKey::HWD => write!(f, "HWD"),
            ImpactCategoryKey::NHWD => write!(f, "NHWD"),
            ImpactCategoryKey::RWD => write!(f, "RWD"),
            ImpactCategoryKey::CRU => write!(f, "CRU"),
            ImpactCategoryKey::MRF => write!(f, "MRF"),
            ImpactCategoryKey::MER => write!(f, "MER"),
            ImpactCategoryKey::EEE => write!(f, "EEE"),
            ImpactCategoryKey::EET => write!(f, "EET"),
            ImpactCategoryKey::PENRE => write!(f, "PENRE"),
            ImpactCategoryKey::GWP_FOS => write!(f, "GWP_FOS"),
            ImpactCategoryKey::GWP_BIO => write!(f, "GWP_BIO"),
            ImpactCategoryKey::GWP_LUL => write!(f, "GWP_LUL"),
            ImpactCategoryKey::EP_FW => write!(f, "EP_FW"),
            ImpactCategoryKey::EP_MAR => write!(f, "EP_MAR"),
            ImpactCategoryKey::EP_TER => write!(f, "EP_TER"),
            ImpactCategoryKey::PM => write!(f, "PM"),
            ImpactCategoryKey::WDP => write!(f, "WDP"),
            ImpactCategoryKey::IRP => write!(f, "IRP"),
            ImpactCategoryKey::ETP_FW => write!(f, "ETP_FW"),
            ImpactCategoryKey::HTP_C => write!(f, "HTP_C"),
            ImpactCategoryKey::HTP_NC => write!(f, "HTP_NC"),
            ImpactCategoryKey::SQP => write!(f, "SQP"),
        }
    }
}

#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq, Debug)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(
    feature = "jsbindings",
    derive(Tsify),
    tsify(into_wasm_abi, from_wasm_abi)
)]
#[cfg_attr(feature = "pybindings", pyclass)]
pub struct ImpactCategory(HashMap<LifeCycleModule, Option<f64>>);

impl ImpactCategory {
    pub fn new() -> Self {
        ImpactCategory(HashMap::new())
    }
    pub fn insert(&mut self, key: LifeCycleModule, value: Option<f64>) -> Option<Option<f64>> {
        self.0.insert(key, value)
    }
    pub fn iter(&self) -> impl Iterator<Item = (&LifeCycleModule, &Option<f64>)> {
        self.0.iter()
    }
    pub fn iter_mut(&mut self) -> IterMut<'_, LifeCycleModule, Option<f64>> {
        self.0.iter_mut()
    }

    pub fn get(&self, key: &LifeCycleModule) -> Option<&Option<f64>> {
        self.0.get(key)
    }

    pub fn get_mut(&mut self, key: &LifeCycleModule) -> Option<&mut Option<f64>> {
        self.0.get_mut(key)
    }
    pub fn remove(&mut self, key: &LifeCycleModule) -> Option<Option<f64>> {
        self.0.remove(key)
    }
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

impl<const N: usize> From<[(LifeCycleModule, Option<f64>); N]> for ImpactCategory {
    fn from(value: [(LifeCycleModule, Option<f64>); N]) -> Self {
        ImpactCategory(HashMap::from(value))
    }
}

#[cfg_attr(feature = "pybindings", pymethods)]
impl ImpactCategory {
    #[cfg(feature = "pybindings")]
    #[new]
    #[pyo3(signature = (value=None))]
    pub fn new_py(value: Option<HashMap<LifeCycleModule, Option<f64>>>) -> Self {
        let mut category = Self::new();
        if value.is_none() {
            return category;
        }
        category.0 = value.unwrap();
        category
    }

    #[cfg(feature = "pybindings")]
    #[classmethod]
    fn from_dict(_cls: &Bound<'_, PyType>, value: HashMap<LifeCycleModule, Option<f64>>) -> Self {
        let mut category = ImpactCategory::new();
        category.0 = value;
        category
    }

    #[cfg(feature = "pybindings")]
    fn dict(&self) -> HashMap<LifeCycleModule, Option<f64>> {
        self.0.clone()
    }

    #[cfg(feature = "pybindings")]
    fn __getitem__(&self, key: &LifeCycleModule) -> Option<f64> {
        self.0.get(key).cloned()?
    }

    #[cfg(feature = "pybindings")]
    fn __repr__(&self) -> String {
        "Impact Category".to_string()
    }

    #[cfg(feature = "pybindings")]
    fn __str__(&self) -> String {
        "Impact Category".to_string()
    }
}

#[derive(Deserialize, Serialize, JsonSchema, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(
    feature = "jsbindings",
    derive(Tsify),
    tsify(into_wasm_abi, from_wasm_abi)
)]
#[cfg_attr(feature = "pybindings", pyclass)]
pub struct Impacts(HashMap<ImpactCategoryKey, ImpactCategory>);

impl Impacts {
    pub fn new() -> Self {
        Impacts(HashMap::new())
    }
    pub fn insert(
        &mut self,
        key: ImpactCategoryKey,
        value: ImpactCategory,
    ) -> Option<ImpactCategory> {
        self.0.insert(key, value)
    }
    pub fn iter(&self) -> impl Iterator<Item = (&ImpactCategoryKey, &ImpactCategory)> {
        self.0.iter()
    }
    pub fn iter_mut(&mut self) -> IterMut<'_, ImpactCategoryKey, ImpactCategory> {
        self.0.iter_mut()
    }
    pub fn get(&self, key: &ImpactCategoryKey) -> Option<&ImpactCategory> {
        self.0.get(key)
    }
    pub fn entry(
        &mut self,
        key: ImpactCategoryKey,
    ) -> Entry<'_, ImpactCategoryKey, ImpactCategory> {
        self.0.entry(key)
    }

    pub fn get_mut(&mut self, key: &ImpactCategoryKey) -> Option<&mut ImpactCategory> {
        self.0.get_mut(key)
    }
    pub fn remove(&mut self, key: &ImpactCategoryKey) -> Option<ImpactCategory> {
        self.0.remove(key)
    }

    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }
}

impl<const N: usize> From<[(ImpactCategoryKey, ImpactCategory); N]> for Impacts {
    fn from(value: [(ImpactCategoryKey, ImpactCategory); N]) -> Self {
        Impacts(HashMap::from(value))
    }
}

#[cfg_attr(feature = "pybindings", pymethods)]
impl Impacts {
    #[cfg(feature = "pybindings")]
    #[new]
    #[pyo3(signature = (value=None))]
    pub fn new_py(value: Option<HashMap<ImpactCategoryKey, ImpactCategory>>) -> Self {
        let mut impacts = Self::new();
        if value.is_none() {
            return impacts;
        }
        impacts.0 = value.unwrap();
        impacts
    }

    #[cfg(feature = "pybindings")]
    #[classmethod]
    fn from_dict(
        _cls: &Bound<'_, PyType>,
        value: HashMap<ImpactCategoryKey, ImpactCategory>,
    ) -> Self {
        let mut impacts = Self::new();
        impacts.0 = value;
        impacts
    }
    #[cfg(feature = "pybindings")]
    fn dict(&self) -> HashMap<ImpactCategoryKey, ImpactCategory> {
        self.0.clone()
    }

    #[cfg(feature = "pybindings")]
    fn __getitem__(&self, key: &ImpactCategoryKey) -> Option<ImpactCategory> {
        self.0.get(key).cloned()
    }

    #[cfg(feature = "pybindings")]
    fn __repr__(&self) -> String {
        "Impacts".to_string()
    }

    #[cfg(feature = "pybindings")]
    fn __str__(&self) -> String {
        "Impacts".to_string()
    }
}

pub trait NewResults {
    fn new_results(
        impact_categories: &Vec<ImpactCategoryKey>,
        life_cycle_stage: &Vec<LifeCycleModule>,
    ) -> Self;
}
impl NewResults for Impacts {
    fn new_results(
        impact_categories: &Vec<ImpactCategoryKey>,
        life_cycle_stage: &Vec<LifeCycleModule>,
    ) -> Self {
        let mut results = Impacts::new();
        impact_categories.iter().for_each(|impact_category_key| {
            let mut impact_category = ImpactCategory::new();
            life_cycle_stage.iter().for_each(|life_cycle_stage| {
                impact_category.insert(life_cycle_stage.clone(), Some(0.0));
            });
            results.insert(impact_category_key.clone(), impact_category);
        });
        results
    }
}
