use crate::Result;
use apache_avro::schema::*;
use std::fmt::Write;

use super::{field_default_value::FieldDefault, field_type::*, global::*};

#[derive(Debug)]
pub struct GeneratedStructFields {
    parent_struct_fullname: String,

    name: SanitizedName,

    doc: String,

    type_name: String,

    serde_with_line: Option<&'static str>,

    default: Option<FieldDefault>,
}

impl GeneratedStructFields {
    pub fn has_default(&self) -> bool {
        self.default.is_some()
    }

    pub fn from(
        field: &RecordField,
        structure_name: &SanitizedName,
        default_namespace: &Option<String>,
    ) -> Result<Self> {
        let field_name = SanitizedName::from_field(&field.name);
        let field_type = get_field_type(&field.schema, default_namespace)?;

        let doc = format_doc(&field.doc, "    ")?;
        let serde_with_line = get_serde_with(field)?;

        let default = match &field.default {
            None => None,
            Some(val) => Some(FieldDefault::from(val, &field.schema)?),
        };

        Ok(GeneratedStructFields {
            name: field_name,
            parent_struct_fullname: structure_name.sanitized_name.to_owned(),
            type_name: field_type,
            serde_with_line,
            doc,
            default,
        })
    }

    pub fn write_struct_declaration_content(&self) -> Result<String> {
        let mut content = self.doc.to_owned();

        if let Some(line) = &self.serde_with_line {
            writeln!(content, "    {line}")?;
        };
        if self.name.is_sanitized {
            writeln!(
                content,
                "    #[serde(rename = \"{}\")]",
                self.name.original_name
            )?
        }
        if self.default.is_some() {
            writeln!(
                content,
                "    #[serde(default = \"{}::default_{}\")]",
                self.parent_struct_fullname, self.name.sanitized_name
            )?;
        }
        writeln!(
            content,
            "    pub {}: {},",
            self.name.sanitized_name, self.type_name
        )?;

        Ok(content)
    }

    pub fn write_struct_default_method_content(&self) -> Result<Option<String>> {
        match &self.default {
            Some(default) => {
                let default_value_str = default
                    .write_content()
                    .map_err(|e| format!("{} => {e}", self.name.sanitized_name))?;

                Ok(format!(
                    "\r\n    #[inline(always)]\r\n    pub fn default_{}() -> {} {{\r\n        {}\r\n    }}\r\n",
                    self.name.sanitized_name, self.type_name, default_value_str
                )
                .into())
            }
            None => Ok(None),
        }
    }
}

fn get_serde_with(field: &RecordField) -> Result<Option<&'static str>> {
    Ok(match field.schema {
        Schema::Date => None,
        Schema::TimeMillis => Some("#[serde(with = \"chrono::naive::serde::ts_milliseconds\")]"),
        Schema::TimeMicros => Some("#[serde(with = \"chrono::naive::serde::ts_microseconds\")]"),
        Schema::TimestampMillis => {
            Some("#[serde(with = \"chrono::naive::serde::ts_milliseconds\")]")
        }
        Schema::TimestampMicros => {
            Some("#[serde(with = \"chrono::naive::serde::ts_microseconds\")]")
        }
        Schema::LocalTimestampMillis => {
            Some("#[serde(with = \"chrono::naive::serde::ts_milliseconds\")]")
        }
        Schema::LocalTimestampMicros => {
            Some("#[serde(with = \"chrono::naive::serde::ts_microseconds\")]")
        }
        _ => None,
    })
}
