use crate::Result;
use apache_avro::{schema::*, Schema};
use std::fmt::Write;
use std::string::*;
use std::*;

use super::field::GeneratedStructFields;
use super::global::*;

#[derive(Debug)]
pub enum GeneratedType {
    None,

    Enum(GeneratedEnum),

    Struct(GeneratedStruct),
}

impl GeneratedType {
    pub fn produce_content(&self) -> Result<String> {
        match self {
            GeneratedType::Enum(x) => x.produce_content(),
            GeneratedType::Struct(x) => x.produce_content(),
            GeneratedType::None => Ok("".to_string()),
        }
    }
    pub fn schema_name(&self) -> String {
        match self {
            GeneratedType::Enum(x) => x.name.original_name.to_owned(),
            GeneratedType::Struct(x) => x.name.original_name.to_owned(),
            GeneratedType::None => "".to_owned(),
        }
    }
}

#[derive(Debug)]
pub struct GeneratedStruct {
    name: SanitizedName,

    schema_doc: String,

    fields: Vec<GeneratedStructFields>,
}

impl GeneratedStruct {
    pub fn produce_content(&self) -> Result<String> {
        let mut content_string = self.schema_doc.to_owned();
        writeln!(
            content_string,
            "#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize, Default)]"
        )?;
        writeln!(content_string, "#[serde(default)]")?;
        if self.name.is_sanitized {
            writeln!(
                content_string,
                "#[serde(rename = \"{}\")]",
                self.name.original_name
            )?
        }
        writeln!(content_string, "pub struct {} {{", self.name.sanitized_name)?;

        for field in self.fields.iter() {
            let field_declaration = field.write_struct_declaration_content()?;
            content_string.push_str(&field_declaration);
        }
        write!(content_string, "}}\r\n\r\n")?;

        write!(content_string, "impl {} {{", self.name.sanitized_name)?;

        if self.fields.iter().any(|f| f.has_default()) {
            for field in self.fields.iter() {
                let field_default_method = field.write_struct_default_method_content()?;
                if let Some(field_default_method) = field_default_method {
                    content_string.push_str(&field_default_method);
                }
            }
        }
        write!(content_string, "}}\r\n\r\n")?;

        Ok(content_string)
    }
}

#[derive(Debug)]
pub struct GeneratedEnum {
    name: SanitizedName,

    schema_doc: String,

    default_record: Option<String>,

    records: Vec<String>,
}

impl GeneratedEnum {
    pub fn produce_content(&self) -> Result<String> {
        let mut content_string = self.schema_doc.to_owned();
        writeln!(
            content_string,
            "#[derive(Debug, PartialEq, Clone, serde::Deserialize, serde::Serialize, Default)]"
        )?;

        if self.name.is_sanitized {
            writeln!(
                content_string,
                "#[serde(rename = \"{}\")]",
                self.name.original_name
            )?
        }
        writeln!(content_string, "pub enum {} {{", self.name.sanitized_name)?;

        if  self.default_record.is_none() {
            writeln!(content_string, "    #[default]")?;
        }

        for enum_record in self.records.iter() {
            if let Some(default_value) = &self.default_record {
                if default_value == enum_record {
                    writeln!(content_string, "    #[default]")?;
                }
            }

            let record_name = SanitizedName::from_type(enum_record);

            if record_name.is_sanitized {
                writeln!(
                    content_string,
                    "    #[serde(rename = \"{}\")]",
                    record_name.original_name
                )?
            }
            writeln!(content_string, "    {},", record_name.sanitized_name)?;
        }
        write!(content_string, "}}\r\n\r\n")?;

        Ok(content_string)
    }
}

impl GeneratedType {
    pub fn generate_schema_struct(
        schema: &Schema,
        default_namespace: &Option<String>,
    ) -> Result<GeneratedType> {
        match schema {
            Schema::Record(i) => {
                Self::treat_record_schema(i, default_namespace).map(GeneratedType::Struct)
            }
            Schema::Array(_) => todo!(),
            Schema::Map(_) => todo!(),
            Schema::Union(_) => todo!(),
            Schema::Enum(enum_schema) => {
                Self::treat_enum_schema(enum_schema).map(GeneratedType::Enum)
            }
            Schema::Fixed(_) => todo!(),
            Schema::Decimal(_) => todo!(),
            Schema::Duration => todo!(),
            Schema::Ref { .. } => todo!(),
            _ => Ok(GeneratedType::None),
        }
    }

    pub fn treat_enum_schema(enum_schema: &EnumSchema) -> Result<GeneratedEnum> {
        let schema_name = SanitizedName::from_type(&enum_schema.name.name);

        let schema_doc = format_doc(&enum_schema.doc, "")?;

        let default_record = enum_schema.default.to_owned();

        let records = enum_schema.symbols.iter().map(|e| e.to_owned()).collect();

        Ok(GeneratedEnum {
            name: schema_name,
            schema_doc,
            default_record,
            records,
        })
    }

    pub fn treat_record_schema(
        record_schema: &RecordSchema,
        default_namespace: &Option<String>,
    ) -> Result<GeneratedStruct> {
        let schema_name = SanitizedName::from_type(&record_schema.name.name);

        let schema_doc = format_doc(&record_schema.doc, "")?;

        let fields: Result<Vec<GeneratedStructFields>> = record_schema
            .fields
            .iter()
            .map(|f| GeneratedStructFields::from(f, &schema_name, default_namespace))
            .collect();

        Ok(GeneratedStruct {
            name: schema_name,
            schema_doc,
            fields: fields?,
        })
    }
}
