use crate::rust::ToRust;
use convert_case::{Case, Casing};
use endpoint_gen_macros::DefinitionVariant;
use endpoint_libs::model::{EndpointSchema, Type};
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use smart_serde_default::smart_serde_default;

/// Marker trait for types that can be used as Definition variants
/// All types used in Definition must implement this to ensure they are properly validatable
// pub trait DefinitionVariant: GenElement<Self> {}

#[derive(Serialize, Deserialize)]
pub enum Definition {
    EndpointSchema(EndpointSchemaDefinition),
    EndpointSchemaList(EndpointSchemaListDefinition),
    Enum(EnumElement),
    EnumList(EnumListDefinition),
    ErrorCodeList(ErrorCodeListDefinition),
    Struct(StructElement),
    StructList(StructListDefinition),
}

impl Definition {
    pub fn validate_self(&self) -> eyre::Result<()> {
        match self {
            Definition::Enum(e) => e.validate_element(),
            Definition::EnumList(list) => list.validate_element(),
            Definition::ErrorCodeList(list) => list.validate_element(),
            Definition::Struct(s) => s.validate_element(),
            Definition::StructList(list) => list.validate_element(),
            Definition::EndpointSchema(schema) => schema.validate_element(),
            Definition::EndpointSchemaList(schemas) => schemas.validate_element(),
        }
    }
}

pub trait GenElement<T: ?Sized>
where
    T: GenElement<T>,
{
    fn validate_element(&self) -> eyre::Result<()>;
}

#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord)]
pub struct ErrorCodeSchema {
    pub name: String,
    pub code: i64,
    #[serde(default)]
    pub description: String,
}

impl ErrorCodeSchema {
    pub fn new(name: impl Into<String>, code: i64, description: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            code,
            description: description.into(),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord, DefinitionVariant)]
pub struct ErrorCodeListDefinition {
    pub codes: Vec<ErrorCodeSchema>,
}

impl GenElement<ErrorCodeListDefinition> for ErrorCodeListDefinition {
    fn validate_element(&self) -> eyre::Result<()> {
        Ok(())
    }
}

/// Wraps the [Type::Enum] variant with extra config
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord, DefinitionVariant)]
pub struct EnumElement {
    #[serde(default)]
    pub config: RustGenConfig,
    pub inner: Type,
}

impl GenElement<EnumElement> for EnumElement {
    fn validate_element(&self) -> eyre::Result<()> {
        match &self.inner {
            Type::Enum { .. } => Ok(()),
            _ => eyre::bail!("Expected enum type"),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord, DefinitionVariant)]
pub struct EnumListDefinition {
    #[serde(default)]
    pub config: RustGenConfig,
    pub enum_elements: Vec<EnumElement>,
}

impl GenElement<EnumListDefinition> for EnumListDefinition {
    fn validate_element(&self) -> eyre::Result<()> {
        if self.enum_elements.iter().all(|e| matches!(e.inner, Type::Enum { .. })) {
            Ok(())
        } else {
            eyre::bail!("Not all elements of the EnumListDefinition are Enum types")
        }
    }
}

impl ToRust for EnumElement {
    fn to_rust_ref(&self, _serde_with: bool) -> String {
        self.validate_element()
            .unwrap_or_else(|_| panic!("EnumElement is invalid: {self:?}"));

        let name = match &self.inner {
            Type::Enum { name, .. } => name.to_case(Case::Pascal),
            _ => unreachable!("The previous validation ensured that this type is a valid Enum"),
        };

        if self.config.prefix_enum {
            format!("Enum{name}")
        } else {
            name
        }
    }

    fn to_rust_decl(&self, serde_with: bool, add_derives: bool) -> String {
        self.validate_element()
            .unwrap_or_else(|_| panic!("EnumElement is invalid: {self:?}"));

        let code_regex = regex::Regex::new(r"=\s*(\d+)").expect("Error building regex to extract endpoint code");

        match &self.inner {
            Type::Enum {
                name: _,
                variants: fields,
            } => {
                let mut fields = fields
                    .iter()
                    .map(|x| {
                        format!(
                            r#"
    /// {}
    {} = {}
"#,
                            x.description,
                            if x.name.chars().last().unwrap().is_lowercase() {
                                x.name.to_case(Case::Pascal)
                            } else {
                                x.name.clone()
                            },
                            x.value
                        )
                    })
                    .sorted_by(|a, b| {
                        // Sort by the endpoint code
                        let code_a = {
                            match code_regex.captures(a) {
                                Some(code) => code[1].parse::<u64>().unwrap_or_else(|err| {
                                    eprintln!("Sorting error: {err}: Rust output may not be sorted correctly");
                                    0
                                }),
                                None => {
                                    eprintln!("Sorting error: Rust output may not be sorted correctly");
                                    0
                                }
                            }
                        };

                        let code_b = {
                            match code_regex.captures(b) {
                                Some(code) => code[1].parse::<u64>().unwrap_or_else(|err| {
                                    eprintln!("Sorting error: {err}: Rust output may not be sorted correctly");
                                    0
                                }),
                                None => {
                                    eprintln!("Sorting error: Rust output may not be sorted correctly");
                                    0
                                }
                            }
                        };

                        code_a.cmp(&code_b)
                    });
                let enum_content = format!(r#"pub enum {} {{{}}}"#, self.to_rust_ref(serde_with), fields.join(","));

                if add_derives {
                    self.add_derives(enum_content)
                } else {
                    enum_content
                }
            }
            _ => unreachable!(),
        }
    }

    fn add_derives(&self, input: String) -> String {
        if self.config.worktable_support {
            format!(
                r#"#[derive(
                    MemStat,
                    Archive,
                    Clone,
                    Copy,
                    Debug,
                    Display,
                    PartialEq,
                    PartialOrd,
                    Eq,
                    Hash,
                    Ord,
                    EnumString,
                    rkyv::Deserialize,
                    rkyv::Serialize,
                    serde::Serialize,
                    serde::Deserialize,
                    {}
                )]
                #[rkyv(compare(PartialEq), derive(Debug))]
                #[repr(u8)]
                {input}
            "#,
                if self.config.json_schema_gen {
                    "JsonSchema,"
                } else {
                    Default::default()
                }
            )
        } else {
            Type::add_default_enum_derives(input)
        }
    }
}
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord, DefinitionVariant)]
pub struct StructListDefinition {
    #[serde(default)]
    pub config: RustGenConfig,
    pub struct_elements: Vec<StructElement>,
}

impl GenElement<StructListDefinition> for StructListDefinition {
    fn validate_element(&self) -> eyre::Result<()> {
        if self
            .struct_elements
            .iter()
            .all(|s| matches!(s.inner, Type::Struct { .. }))
        {
            Ok(())
        } else {
            eyre::bail!("Not all elements of the StructListDefinition are Struct types")
        }
    }
}

/// Wraps the [Type::Struct] variant with extra config
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord, DefinitionVariant)]
pub struct StructElement {
    #[serde(default)]
    pub config: RustGenConfig,
    pub inner: Type,
}

impl GenElement<StructElement> for StructElement {
    fn validate_element(&self) -> eyre::Result<()> {
        match &self.inner {
            Type::Struct { .. } => Ok(()),
            _ => eyre::bail!("Expected struct type"),
        }
    }
}

impl ToRust for StructElement {
    fn to_rust_ref(&self, _serde_with: bool) -> String {
        self.validate_element()
            .unwrap_or_else(|_| panic!("StructElement is invalid: {self:?}"));

        match &self.inner {
            Type::Struct { name, .. } => name.clone(),
            _ => unreachable!("The previous validation ensured that this type is a valid Struct"),
        }
    }

    fn to_rust_decl(&self, serde_with: bool, add_derives: bool) -> String {
        self.validate_element()
            .unwrap_or_else(|_| panic!("StructElement is invalid: {self:?}"));

        let (name, fields) = match &self.inner {
            Type::Struct { name, fields } => (name.to_case(Case::Pascal), fields),
            _ => unreachable!("The previous validation ensured that this type is a valid Struct"),
        };

        let mut fields = fields.iter().map(|x| {
            let opt = matches!(&x.ty, Type::Optional(_));
            let serde_with_opt = match &x.ty {
                Type::BlockchainDecimal => "rust_decimal::serde::str",
                Type::BlockchainAddress if serde_with => "WithBlockchainAddress",
                Type::BlockchainTransactionHash if serde_with => "WithBlockchainTransactionHash",
                // TODO: handle optional decimals
                // Type::Optional(t) if matches!(**t, Type::BlockchainDecimal) => {
                //     "WithBlockchainDecimal"
                // }
                // Type::Optional(t) if matches!(**t, Type::BlockchainAddress) => {
                //     "WithBlockchainAddress"
                // }
                // Type::Optional(t) if matches!(**t, Type::BlockchainTransactionHash) => {
                //     "WithBlockchainTransactionHash"
                // }
                _ => "",
            };
            format!(
                "{} {} pub {}: {}",
                if opt { "#[serde(default)]" } else { "" },
                if serde_with_opt.is_empty() {
                    "".to_string()
                } else {
                    format!("#[serde(with = \"{serde_with_opt}\")]")
                },
                x.name,
                x.ty.to_rust_ref(serde_with)
            )
        });
        let input = format!("pub struct {} {{{}}}", name, fields.join(","));

        if add_derives { self.add_derives(input) } else { input }
    }

    fn add_derives(&self, input: String) -> String {
        if self.config.worktable_support {
            // format!(
            //     r#"#[derive(
            //             Clone,
            //             Copy,
            //             Debug,
            //             Default,
            //             Eq,
            //             Hash,
            //             Ord,
            //             PartialEq,
            //             PartialOrd,
            //             derive_more::Display,
            //             derive_more::From,
            //             derive_more::FromStr,
            //             derive_more::Into,
            //             MemStat,
            //             rkyv::Archive,
            //             SizeMeasure,
            //             rkyv::Deserialize,
            //             rkyv::Serialize,
            //             serde::Serialize,
            //             serde::Deserialize,
            //             derive_more::Display,
            //         )]
            //         #[rkyv(compare(PartialEq), derive(Debug, PartialOrd, PartialEq, Eq, Ord))]
            //         {input}
            //     "#

            // TODO: Fix worktable support for structs
            format!(
                r#" #[derive(Serialize, Deserialize, Debug, Clone, {})]
                #[serde(rename_all = "camelCase")]
                {input}
            "#,
                if self.config.json_schema_gen {
                    "JsonSchema,"
                } else {
                    Default::default()
                }
            )
        } else {
            Type::add_default_struct_derives(input)
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
pub struct RustGenConfig {
    #[serde(default)]
    pub prefix_enum: bool,
    #[serde(default)]
    pub worktable_support: bool,
    #[serde(default)]
    pub json_schema_gen: bool,
    #[serde(default)]
    pub snake_case_fields: bool,
    #[serde(default)]
    pub override_parent: bool,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct GenService {
    pub name: String,
    pub id: u16,
    pub endpoints: Vec<EndpointSchemaElement>,
}

impl GenService {
    pub fn new(name: String, id: u16, endpoints: Vec<EndpointSchemaElement>) -> Self {
        Self { name, id, endpoints }
    }
}

#[derive(Serialize, Deserialize)]
pub struct EndpointSchemaDefinition {
    pub service_name: String,
    pub service_id: u16,
    pub schema: EndpointSchemaElement,
}

#[smart_serde_default]
#[derive(Serialize, Deserialize, SmartDefault, Clone)]
pub struct EndpointSchemaElement {
    #[smart_default(true)]
    pub frontend_facing: bool,
    #[serde(default)]
    pub config: RustGenConfig,
    pub schema: EndpointSchema,
}

impl From<EndpointSchemaElement> for EndpointSchema {
    fn from(val: EndpointSchemaElement) -> Self {
        EndpointSchema {
            name: val.schema.name,
            code: val.schema.code,
            parameters: val.schema.parameters,
            returns: val.schema.returns,
            stream_response: val.schema.stream_response,
            description: val.schema.description,
            json_schema: val.schema.json_schema,
            roles: val.schema.roles,
            errors: val.schema.errors,
        }
    }
}

impl FromIterator<EndpointSchemaElement> for Vec<EndpointSchema> {
    fn from_iter<T: IntoIterator<Item = EndpointSchemaElement>>(iter: T) -> Self {
        iter.into_iter().map(|element| element.schema).collect()
    }
}

impl GenElement<EndpointSchemaDefinition> for EndpointSchemaDefinition {
    fn validate_element(&self) -> eyre::Result<()> {
        Ok(())
    }
}

#[derive(Serialize, Deserialize, DefinitionVariant)]
pub struct EndpointSchemaListDefinition {
    pub service_name: String,
    pub service_id: u16,
    #[serde(default)]
    pub config: RustGenConfig,
    pub endpoints: Vec<EndpointSchemaElement>,
}

impl GenElement<EndpointSchemaListDefinition> for EndpointSchemaListDefinition {
    fn validate_element(&self) -> eyre::Result<()> {
        Ok(())
    }
}
