use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[cfg(feature = "python")]
use pyo3::{
    prelude::*,
    types::{PyDateTime, PyDict},
};

use crate::native::deserializers::{
    default_datetime_none, default_string_none, deserialize_empty_string_as_none,
    deserialize_empty_string_as_none_datetime,
};

#[cfg(feature = "python")]
use crate::native::deserializers::{to_py_datetime, to_py_datetime_option};

#[cfg(not(feature = "python"))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Value {
    #[serde(rename = "by")]
    #[serde(alias = "@by")]
    #[serde(alias = "by")]
    pub by: String,

    #[serde(rename = "byUniqueId")]
    #[serde(alias = "@byUniqueId")]
    #[serde(alias = "byUniqueId")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub by_unique_id: Option<String>,
    #[serde(rename = "role")]
    #[serde(alias = "@role")]
    #[serde(alias = "role")]
    pub role: String,
    #[serde(rename = "when")]
    #[serde(alias = "@when")]
    #[serde(alias = "when")]
    pub when: Option<DateTime<Utc>>,

    #[serde(rename = "value")]
    #[serde(alias = "$text")]
    #[serde(alias = "#text")]
    #[serde(alias = "value")]
    #[serde(default)]
    pub value: String,
}

#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[pyclass(skip_from_py_object)]
pub struct Value {
    #[serde(rename = "by")]
    #[serde(alias = "@by")]
    #[serde(alias = "by")]
    pub by: String,

    #[serde(rename = "byUniqueId")]
    #[serde(alias = "@byUniqueId")]
    #[serde(alias = "byUniqueId")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub by_unique_id: Option<String>,
    #[serde(rename = "role")]
    #[serde(alias = "@role")]
    #[serde(alias = "role")]
    pub role: String,
    #[serde(rename = "when")]
    #[serde(alias = "@when")]
    #[serde(alias = "when")]
    pub when: Option<DateTime<Utc>>,

    #[serde(rename = "value")]
    #[serde(alias = "$text")]
    #[serde(alias = "#text")]
    #[serde(alias = "value")]
    #[serde(default)]
    pub value: String,
}

#[cfg(feature = "python")]
#[pymethods]
impl Value {
    #[getter]
    fn by(&self) -> PyResult<String> {
        Ok(self.by.clone())
    }

    #[getter]
    fn by_unique_id(&self) -> PyResult<Option<String>> {
        Ok(self.by_unique_id.clone())
    }

    #[getter]
    fn role(&self) -> PyResult<String> {
        Ok(self.role.clone())
    }

    #[getter]
    fn when<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
        to_py_datetime_option(py, &self.when)
    }

    #[getter]
    fn value(&self) -> PyResult<String> {
        Ok(self.value.clone())
    }

    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("by", &self.by)?;
        dict.set_item("by_unique_id", &self.by_unique_id)?;
        dict.set_item("role", &self.role)?;
        dict.set_item("when", to_py_datetime_option(py, &self.when)?)?;
        dict.set_item("value", &self.value)?;

        Ok(dict)
    }
}

#[cfg(not(feature = "python"))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Reason {
    #[serde(rename = "by")]
    #[serde(alias = "@by")]
    #[serde(alias = "by")]
    pub by: String,

    #[serde(rename = "byUniqueId")]
    #[serde(alias = "@byUniqueId")]
    #[serde(alias = "byUniqueId")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub by_unique_id: Option<String>,

    #[serde(rename = "role")]
    #[serde(alias = "@role")]
    #[serde(alias = "role")]
    pub role: String,
    #[serde(rename = "when")]
    #[serde(alias = "@when")]
    #[serde(alias = "when")]
    pub when: Option<DateTime<Utc>>,

    #[serde(rename = "value")]
    #[serde(alias = "$text")]
    #[serde(alias = "#text")]
    #[serde(alias = "value")]
    #[serde(default)]
    pub value: String,
}

#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[pyclass(skip_from_py_object)]
pub struct Reason {
    #[serde(rename = "by")]
    #[serde(alias = "@by")]
    #[serde(alias = "by")]
    pub by: String,

    #[serde(rename = "byUniqueId")]
    #[serde(alias = "@byUniqueId")]
    #[serde(alias = "byUniqueId")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub by_unique_id: Option<String>,

    #[serde(rename = "role")]
    #[serde(alias = "@role")]
    #[serde(alias = "role")]
    pub role: String,
    #[serde(rename = "when")]
    #[serde(alias = "@when")]
    #[serde(alias = "when")]
    pub when: Option<DateTime<Utc>>,

    #[serde(rename = "value")]
    #[serde(alias = "$text")]
    #[serde(alias = "#text")]
    #[serde(alias = "value")]
    #[serde(default)]
    pub value: String,
}

#[cfg(feature = "python")]
#[pymethods]
impl Reason {
    #[getter]
    fn by(&self) -> PyResult<String> {
        Ok(self.by.clone())
    }

    #[getter]
    fn by_unique_id(&self) -> PyResult<Option<String>> {
        Ok(self.by_unique_id.clone())
    }

    #[getter]
    fn role(&self) -> PyResult<String> {
        Ok(self.role.clone())
    }

    #[getter]
    fn when<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
        to_py_datetime_option(py, &self.when)
    }

    #[getter]
    fn value(&self) -> PyResult<String> {
        Ok(self.value.clone())
    }

    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("by", &self.by)?;
        dict.set_item("by_unique_id", &self.by_unique_id)?;
        dict.set_item("role", &self.role)?;
        dict.set_item("when", to_py_datetime_option(py, &self.when)?)?;
        dict.set_item("value", &self.value)?;

        Ok(dict)
    }
}

#[cfg(not(feature = "python"))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Entry {
    #[serde(rename = "entryId")]
    #[serde(alias = "@id")]
    #[serde(alias = "entryId")]
    pub entry_id: String,

    #[serde(rename = "reviewedBy")]
    #[serde(alias = "@reviewedBy")]
    #[serde(alias = "reviewedBy")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub reviewed_by: Option<String>,

    #[serde(rename = "reviewedByUniqueId")]
    #[serde(alias = "@reviewedByUniqueId")]
    #[serde(alias = "reviewedByUniqueId")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub reviewed_by_unique_id: Option<String>,

    #[serde(rename = "reviewedByWhen")]
    #[serde(alias = "@reviewedByWhen")]
    #[serde(alias = "reviewedByWhen")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub reviewed_by_when: Option<DateTime<Utc>>,

    pub value: Option<Value>,
    pub reason: Option<Reason>,
}

#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[pyclass(skip_from_py_object)]
pub struct Entry {
    #[serde(rename = "entryId")]
    #[serde(alias = "@id")]
    #[serde(alias = "entryId")]
    pub entry_id: String,

    #[serde(rename = "reviewedBy")]
    #[serde(alias = "@reviewedBy")]
    #[serde(alias = "reviewedBy")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub reviewed_by: Option<String>,

    #[serde(rename = "reviewedByUniqueId")]
    #[serde(alias = "@reviewedByUniqueId")]
    #[serde(alias = "reviewedByUniqueId")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub reviewed_by_unique_id: Option<String>,

    #[serde(rename = "reviewedByWhen")]
    #[serde(alias = "@reviewedByWhen")]
    #[serde(alias = "reviewedByWhen")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub reviewed_by_when: Option<DateTime<Utc>>,

    pub value: Option<Value>,
    pub reason: Option<Reason>,
}

#[cfg(feature = "python")]
#[pymethods]
impl Entry {
    #[getter]
    fn entry_id(&self) -> PyResult<String> {
        Ok(self.entry_id.clone())
    }

    #[getter]
    fn reviewed_by(&self) -> PyResult<Option<String>> {
        Ok(self.reviewed_by.clone())
    }

    #[getter]
    fn reviewed_by_unique_id(&self) -> PyResult<Option<String>> {
        Ok(self.reviewed_by_unique_id.clone())
    }

    #[getter]
    fn reviewed_by_when<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
        to_py_datetime_option(py, &self.reviewed_by_when)
    }

    #[getter]
    fn value(&self) -> PyResult<Option<Value>> {
        Ok(self.value.clone())
    }

    #[getter]
    fn reason(&self) -> PyResult<Option<Reason>> {
        Ok(self.reason.clone())
    }

    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("entry_id", &self.entry_id)?;
        dict.set_item("reviewed_by", &self.reviewed_by)?;
        dict.set_item("reviewed_by_unique_id", &self.reviewed_by_unique_id)?;
        dict.set_item(
            "reviewed_by_when",
            to_py_datetime_option(py, &self.reviewed_by_when)?,
        )?;
        if let Some(value) = &self.value {
            dict.set_item("value", value.to_dict(py)?)?;
        } else {
            dict.set_item("value", py.None())?;
        }
        if let Some(reason) = &self.reason {
            dict.set_item("reason", reason.to_dict(py)?)?;
        } else {
            dict.set_item("reason", py.None())?;
        }

        Ok(dict)
    }
}

#[cfg(not(feature = "python"))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Comment {
    #[serde(rename = "commentId")]
    #[serde(alias = "@id")]
    #[serde(alias = "commentId")]
    pub comment_id: String,
    pub value: Option<Value>,
}

#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[pyclass(get_all, skip_from_py_object)]
pub struct Comment {
    #[serde(rename = "commentId")]
    #[serde(alias = "@id")]
    #[serde(alias = "commentId")]
    pub comment_id: String,
    pub value: Option<Value>,
}

#[cfg(feature = "python")]
#[pymethods]
impl Comment {
    #[getter]
    fn comment_id(&self) -> PyResult<String> {
        Ok(self.comment_id.clone())
    }

    #[getter]
    fn value(&self) -> PyResult<Option<Value>> {
        Ok(self.value.clone())
    }

    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("comment_id", &self.comment_id)?;
        if let Some(value) = &self.value {
            dict.set_item("value", value.to_dict(py)?)?;
        } else {
            dict.set_item("value", py.None())?;
        }

        Ok(dict)
    }
}

#[cfg(not(feature = "python"))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Field {
    #[serde(rename = "name")]
    #[serde(alias = "@name")]
    #[serde(alias = "name")]
    pub name: String,

    #[serde(rename = "fieldType")]
    #[serde(alias = "@type")]
    #[serde(alias = "fieldType")]
    pub field_type: String,

    #[serde(rename = "dataType")]
    #[serde(alias = "@dataType")]
    #[serde(alias = "dataType")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub data_type: Option<String>,
    #[serde(rename = "errorCode")]
    #[serde(alias = "@errorCode")]
    #[serde(alias = "errorCode")]
    pub error_code: String,
    #[serde(rename = "whenCreated")]
    #[serde(alias = "@whenCreated")]
    #[serde(alias = "whenCreated")]
    pub when_created: Option<DateTime<Utc>>,
    #[serde(rename = "keepHistory")]
    #[serde(alias = "@keepHistory")]
    #[serde(alias = "keepHistory")]
    pub keep_history: bool,

    #[serde(alias = "entry")]
    pub entries: Option<Vec<Entry>>,

    #[serde(alias = "comment")]
    pub comments: Option<Vec<Comment>>,
}

#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[pyclass(skip_from_py_object)]
pub struct Field {
    #[serde(rename = "name")]
    #[serde(alias = "@name")]
    #[serde(alias = "name")]
    pub name: String,

    #[serde(rename = "fieldType")]
    #[serde(alias = "@type")]
    #[serde(alias = "fieldType")]
    pub field_type: String,

    #[serde(rename = "dataType")]
    #[serde(alias = "@dataType")]
    #[serde(alias = "dataType")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub data_type: Option<String>,

    #[serde(rename = "errorCode")]
    #[serde(alias = "@errorCode")]
    #[serde(alias = "errorCode")]
    pub error_code: String,
    #[serde(rename = "whenCreated")]
    #[serde(alias = "@whenCreated")]
    #[serde(alias = "whenCreated")]
    pub when_created: Option<DateTime<Utc>>,
    #[serde(rename = "keepHistory")]
    #[serde(alias = "@keepHistory")]
    #[serde(alias = "keepHistory")]
    pub keep_history: bool,

    #[serde(alias = "entry")]
    pub entries: Option<Vec<Entry>>,

    #[serde(alias = "comment")]
    pub comments: Option<Vec<Comment>>,
}

#[cfg(feature = "python")]
#[pymethods]
impl Field {
    #[getter]
    fn name(&self) -> PyResult<String> {
        Ok(self.name.clone())
    }

    #[getter]
    fn field_type(&self) -> PyResult<String> {
        Ok(self.field_type.clone())
    }

    #[getter]
    fn data_type(&self) -> PyResult<Option<String>> {
        Ok(self.data_type.clone())
    }

    #[getter]
    fn error_code(&self) -> PyResult<String> {
        Ok(self.error_code.clone())
    }

    #[getter]
    fn when_created<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
        self.when_created
            .as_ref()
            .map(|dt| to_py_datetime(py, dt))
            .transpose()
    }

    #[getter]
    fn keep_history(&self) -> PyResult<bool> {
        Ok(self.keep_history)
    }

    #[getter]
    fn entries(&self) -> PyResult<Option<Vec<Entry>>> {
        Ok(self.entries.clone())
    }

    #[getter]
    fn comments(&self) -> PyResult<Option<Vec<Comment>>> {
        Ok(self.comments.clone())
    }

    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("name", &self.name)?;
        dict.set_item("field_type", &self.field_type)?;
        dict.set_item("data_type", &self.data_type)?;
        dict.set_item("error_code", &self.error_code)?;
        dict.set_item(
            "when_created",
            self.when_created
                .as_ref()
                .map(|dt| to_py_datetime(py, dt))
                .transpose()?,
        )?;
        dict.set_item("keep_history", self.keep_history)?;

        let mut entry_dicts = Vec::new();
        if let Some(entries) = &self.entries {
            for entry in entries {
                let entry_dict = entry.to_dict(py)?;
                entry_dicts.push(entry_dict);
            }
            dict.set_item("entries", entry_dicts)?;
        } else {
            dict.set_item("entries", py.None())?;
        }

        let mut comment_dicts = Vec::new();
        if let Some(comments) = &self.comments {
            for comment in comments {
                let comment_dict = comment.to_dict(py)?;
                comment_dicts.push(comment_dict);
            }
            dict.set_item("comments", comment_dicts)?;
        } else {
            dict.set_item("comments", py.None())?;
        }

        Ok(dict)
    }
}

#[cfg(not(feature = "python"))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Category {
    #[serde(rename = "name")]
    #[serde(alias = "@name")]
    #[serde(alias = "name")]
    pub name: String,

    #[serde(rename = "categoryType")]
    #[serde(alias = "@type")]
    #[serde(alias = "categoryType")]
    pub category_type: String,

    #[serde(rename = "highestIndex")]
    #[serde(alias = "@highestIndex")]
    #[serde(alias = "highestIndex")]
    pub highest_index: usize,

    #[serde(alias = "field")]
    pub fields: Option<Vec<Field>>,
}

#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[pyclass(get_all, skip_from_py_object)]
pub struct Category {
    #[serde(rename = "name")]
    #[serde(alias = "@name")]
    #[serde(alias = "name")]
    pub name: String,

    #[serde(rename = "categoryType")]
    #[serde(alias = "@type")]
    #[serde(alias = "categoryType")]
    pub category_type: String,

    #[serde(rename = "highestIndex")]
    #[serde(alias = "@highestIndex")]
    #[serde(alias = "highestIndex")]
    pub highest_index: usize,

    #[serde(alias = "field")]
    pub fields: Option<Vec<Field>>,
}

#[cfg(feature = "python")]
#[pymethods]
impl Category {
    #[getter]
    fn name(&self) -> PyResult<String> {
        Ok(self.name.clone())
    }

    #[getter]
    fn category_type(&self) -> PyResult<String> {
        Ok(self.category_type.clone())
    }

    #[getter]
    fn highest_index(&self) -> PyResult<usize> {
        Ok(self.highest_index)
    }

    #[getter]
    fn fields(&self) -> PyResult<Option<Vec<Field>>> {
        Ok(self.fields.clone())
    }

    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("name", &self.name)?;
        dict.set_item("category_type", &self.category_type)?;
        dict.set_item("highest_index", self.highest_index)?;

        let mut field_dicts = Vec::new();
        if let Some(fields) = &self.fields {
            for field in fields {
                let field_dict = field.to_dict(py)?;
                field_dicts.push(field_dict);
            }
            dict.set_item("fields", field_dicts)?;
        } else {
            dict.set_item("fields", py.None())?;
        }

        Ok(dict)
    }
}

impl Form {
    pub fn from_attributes(
        attrs: std::collections::HashMap<String, String>,
    ) -> Result<Self, crate::errors::Error> {
        let name = attrs.get("name").cloned().unwrap_or_default();

        let last_modified = if let Some(lm) = attrs.get("lastModified") {
            if lm.is_empty() {
                None
            } else {
                parse_datetime_internal(lm).ok()
            }
        } else {
            None
        };

        let who_last_modified_name = attrs
            .get("whoLastModifiedName")
            .filter(|s| !s.is_empty())
            .cloned();
        let who_last_modified_role = attrs
            .get("whoLastModifiedRole")
            .filter(|s| !s.is_empty())
            .cloned();

        let when_created = attrs
            .get("whenCreated")
            .and_then(|s| s.parse().ok())
            .unwrap_or(0);

        let has_errors = attrs.get("hasErrors").map(|s| s == "true").unwrap_or(false);

        let has_warnings = attrs
            .get("hasWarnings")
            .map(|s| s == "true")
            .unwrap_or(false);

        let locked = attrs.get("locked").map(|s| s == "true").unwrap_or(false);

        let user = attrs.get("user").filter(|s| !s.is_empty()).cloned();

        let date_time_changed = if let Some(dtc) = attrs.get("dateTimeChanged") {
            if dtc.is_empty() {
                None
            } else {
                parse_datetime_internal(dtc).ok()
            }
        } else {
            None
        };

        let form_title = attrs.get("formTitle").cloned().unwrap_or_default();

        let form_index = attrs
            .get("formIndex")
            .and_then(|s| s.parse().ok())
            .unwrap_or(0);

        let form_group = attrs.get("formGroup").filter(|s| !s.is_empty()).cloned();
        let form_state = attrs.get("formState").cloned().unwrap_or_default();

        Ok(Form {
            name,
            last_modified,
            who_last_modified_name,
            who_last_modified_role,
            when_created,
            has_errors,
            has_warnings,
            locked,
            user,
            date_time_changed,
            form_title,
            form_index,
            form_group,
            form_state,
            states: None,
            lock_state: None,
            categories: None,
        })
    }
}

fn parse_datetime_internal(s: &str) -> Result<DateTime<Utc>, crate::errors::Error> {
    if let Ok(dt) = chrono::DateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S %z") {
        Ok(dt.with_timezone(&Utc))
    } else if let Ok(dt) = chrono::DateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S%z") {
        Ok(dt.with_timezone(&Utc))
    } else if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(s) {
        Ok(dt.with_timezone(&Utc))
    } else {
        Err(crate::errors::Error::ParsingError(
            quick_xml::de::DeError::Custom(format!("Invalid datetime format: {}", s)),
        ))
    }
}

#[cfg(not(feature = "python"))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct State {
    #[serde(rename = "value")]
    #[serde(alias = "@value")]
    #[serde(alias = "value")]
    pub value: String,
    #[serde(rename = "signer")]
    #[serde(alias = "@signer")]
    #[serde(alias = "signer")]
    pub signer: String,
    #[serde(rename = "signerUniqueId")]
    #[serde(alias = "@signerUniqueId")]
    #[serde(alias = "signerUniqueId")]
    pub signer_unique_id: String,

    #[serde(rename = "dateSigned")]
    #[serde(alias = "@dateSigned")]
    #[serde(alias = "dateSigned")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub date_signed: Option<DateTime<Utc>>,
}

#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[pyclass(skip_from_py_object)]
pub struct State {
    #[serde(rename = "value")]
    #[serde(alias = "@value")]
    #[serde(alias = "value")]
    pub value: String,
    #[serde(rename = "signer")]
    #[serde(alias = "@signer")]
    #[serde(alias = "signer")]
    pub signer: String,
    #[serde(rename = "signerUniqueId")]
    #[serde(alias = "@signerUniqueId")]
    #[serde(alias = "signerUniqueId")]
    pub signer_unique_id: String,

    #[serde(rename = "dateSigned")]
    #[serde(alias = "@dateSigned")]
    #[serde(alias = "dateSigned")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub date_signed: Option<DateTime<Utc>>,
}

#[cfg(feature = "python")]
#[pymethods]
impl State {
    #[getter]
    fn value(&self) -> PyResult<String> {
        Ok(self.value.clone())
    }

    #[getter]
    fn signer(&self) -> PyResult<String> {
        Ok(self.signer.clone())
    }

    #[getter]
    fn signer_unique_id(&self) -> PyResult<String> {
        Ok(self.signer_unique_id.clone())
    }

    #[getter]
    fn date_signed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
        to_py_datetime_option(py, &self.date_signed)
    }

    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("value", &self.value)?;
        dict.set_item("signer", &self.signer)?;
        dict.set_item("signer_unique_id", &self.signer_unique_id)?;
        dict.set_item("date_signed", to_py_datetime_option(py, &self.date_signed)?)?;

        Ok(dict)
    }
}

#[cfg(not(feature = "python"))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct LockState {
    #[serde(rename = "locked")]
    #[serde(alias = "@locked")]
    #[serde(alias = "locked")]
    pub locked: bool,

    #[serde(rename = "user")]
    #[serde(alias = "@user")]
    #[serde(alias = "user")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub user: Option<String>,

    #[serde(rename = "userUniqueId")]
    #[serde(alias = "@userUniqueId")]
    #[serde(alias = "userUniqueId")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub user_unique_id: Option<String>,

    #[serde(rename = "dateTimeChanged")]
    #[serde(alias = "@dateTimeChanged")]
    #[serde(alias = "dateTimeChanged")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub date_time_changed: Option<DateTime<Utc>>,
}

#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[pyclass(skip_from_py_object)]
pub struct LockState {
    #[serde(rename = "locked")]
    #[serde(alias = "@locked")]
    #[serde(alias = "locked")]
    pub locked: bool,

    #[serde(rename = "user")]
    #[serde(alias = "@user")]
    #[serde(alias = "user")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub user: Option<String>,

    #[serde(rename = "userUniqueId")]
    #[serde(alias = "@userUniqueId")]
    #[serde(alias = "userUniqueId")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub user_unique_id: Option<String>,

    #[serde(rename = "dateTimeChanged")]
    #[serde(alias = "@dateTimeChanged")]
    #[serde(alias = "dateTimeChanged")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub date_time_changed: Option<DateTime<Utc>>,
}

#[cfg(feature = "python")]
#[pymethods]
impl LockState {
    #[getter]
    fn locked(&self) -> PyResult<bool> {
        Ok(self.locked)
    }

    #[getter]
    fn user(&self) -> PyResult<Option<String>> {
        Ok(self.user.clone())
    }

    #[getter]
    fn user_unique_id(&self) -> PyResult<Option<String>> {
        Ok(self.user_unique_id.clone())
    }

    #[getter]
    fn date_time_changed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
        to_py_datetime_option(py, &self.date_time_changed)
    }

    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("locked", self.locked)?;
        dict.set_item("user", &self.user)?;
        dict.set_item("user_unique_id", &self.user_unique_id)?;
        dict.set_item(
            "date_time_changed",
            to_py_datetime_option(py, &self.date_time_changed)?,
        )?;

        Ok(dict)
    }
}

#[cfg(not(feature = "python"))]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Form {
    #[serde(rename = "name")]
    #[serde(alias = "@name")]
    #[serde(alias = "name")]
    pub name: String,

    #[serde(rename = "lastModified")]
    #[serde(alias = "@lastModified")]
    #[serde(alias = "lastModified")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub last_modified: Option<DateTime<Utc>>,

    #[serde(rename = "whoLastModifiedName")]
    #[serde(alias = "@whoLastModifiedName")]
    #[serde(alias = "whoLastModifiedName")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub who_last_modified_name: Option<String>,

    #[serde(rename = "whoLastModifiedRole")]
    #[serde(alias = "@whoLastModifiedRole")]
    #[serde(alias = "whoLastModifiedRole")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub who_last_modified_role: Option<String>,

    #[serde(rename = "whenCreated")]
    #[serde(alias = "@whenCreated")]
    #[serde(alias = "whenCreated")]
    pub when_created: usize,
    #[serde(rename = "hasErrors")]
    #[serde(alias = "@hasErrors")]
    #[serde(alias = "hasErrors")]
    pub has_errors: bool,
    #[serde(rename = "hasWarnings")]
    #[serde(alias = "@hasWarnings")]
    #[serde(alias = "hasWarnings")]
    pub has_warnings: bool,
    #[serde(rename = "locked")]
    #[serde(alias = "@locked")]
    #[serde(alias = "locked")]
    pub locked: bool,

    #[serde(rename = "user")]
    #[serde(alias = "@user")]
    #[serde(alias = "user")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub user: Option<String>,

    #[serde(rename = "dateTimeChanged")]
    #[serde(alias = "@dateTimeChanged")]
    #[serde(alias = "dateTimeChanged")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub date_time_changed: Option<DateTime<Utc>>,

    #[serde(rename = "formTitle")]
    #[serde(alias = "@formTitle")]
    #[serde(alias = "formTitle")]
    pub form_title: String,
    #[serde(rename = "formIndex")]
    #[serde(alias = "@formIndex")]
    #[serde(alias = "formIndex")]
    pub form_index: usize,

    #[serde(rename = "formGroup")]
    #[serde(alias = "@formGroup")]
    #[serde(alias = "formGroup")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub form_group: Option<String>,

    #[serde(rename = "formState")]
    #[serde(alias = "@formState")]
    #[serde(alias = "formState")]
    pub form_state: String,

    #[serde(alias = "state")]
    pub states: Option<Vec<State>>,

    #[serde(alias = "lockState")]
    pub lock_state: Option<LockState>,

    #[serde(alias = "category")]
    pub categories: Option<Vec<Category>>,
}

#[cfg(feature = "python")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[pyclass(skip_from_py_object)]
pub struct Form {
    #[serde(rename = "name")]
    #[serde(alias = "@name")]
    #[serde(alias = "name")]
    pub name: String,

    #[serde(rename = "lastModified")]
    #[serde(alias = "@lastModified")]
    #[serde(alias = "lastModified")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub last_modified: Option<DateTime<Utc>>,

    #[serde(rename = "whoLastModifiedName")]
    #[serde(alias = "@whoLastModifiedName")]
    #[serde(alias = "whoLastModifiedName")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub who_last_modified_name: Option<String>,

    #[serde(rename = "whoLastModifiedRole")]
    #[serde(alias = "@whoLastModifiedRole")]
    #[serde(alias = "whoLastModifiedRole")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub who_last_modified_role: Option<String>,

    #[serde(rename = "whenCreated")]
    #[serde(alias = "@whenCreated")]
    #[serde(alias = "whenCreated")]
    pub when_created: usize,
    #[serde(rename = "hasErrors")]
    #[serde(alias = "@hasErrors")]
    #[serde(alias = "hasErrors")]
    pub has_errors: bool,
    #[serde(rename = "hasWarnings")]
    #[serde(alias = "@hasWarnings")]
    #[serde(alias = "hasWarnings")]
    pub has_warnings: bool,
    #[serde(rename = "locked")]
    #[serde(alias = "@locked")]
    #[serde(alias = "locked")]
    pub locked: bool,

    #[serde(rename = "user")]
    #[serde(alias = "@user")]
    #[serde(alias = "user")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub user: Option<String>,

    #[serde(rename = "dateTimeChanged")]
    #[serde(alias = "@dateTimeChanged")]
    #[serde(alias = "dateTimeChanged")]
    #[serde(
        default = "default_datetime_none",
        deserialize_with = "deserialize_empty_string_as_none_datetime"
    )]
    pub date_time_changed: Option<DateTime<Utc>>,

    #[serde(rename = "formTitle")]
    #[serde(alias = "@formTitle")]
    #[serde(alias = "formTitle")]
    pub form_title: String,
    #[serde(rename = "formIndex")]
    #[serde(alias = "@formIndex")]
    #[serde(alias = "formIndex")]
    pub form_index: usize,

    #[serde(rename = "formGroup")]
    #[serde(alias = "@formGroup")]
    #[serde(alias = "formGroup")]
    #[serde(
        default = "default_string_none",
        deserialize_with = "deserialize_empty_string_as_none"
    )]
    pub form_group: Option<String>,

    #[serde(rename = "formState")]
    #[serde(alias = "@formState")]
    #[serde(alias = "formState")]
    pub form_state: String,

    #[serde(alias = "state")]
    pub states: Option<Vec<State>>,

    #[serde(alias = "lockState")]
    pub lock_state: Option<LockState>,

    #[serde(alias = "category")]
    pub categories: Option<Vec<Category>>,
}

#[cfg(feature = "python")]
#[pymethods]
impl Form {
    #[getter]
    fn name(&self) -> PyResult<String> {
        Ok(self.name.clone())
    }

    #[getter]
    fn last_modified<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
        to_py_datetime_option(py, &self.last_modified)
    }

    #[getter]
    fn who_last_modified_name(&self) -> PyResult<Option<String>> {
        Ok(self.who_last_modified_name.clone())
    }

    #[getter]
    fn who_last_modified_role(&self) -> PyResult<Option<String>> {
        Ok(self.who_last_modified_role.clone())
    }

    #[getter]
    fn when_created(&self) -> PyResult<usize> {
        Ok(self.when_created)
    }

    #[getter]
    fn has_errors(&self) -> PyResult<bool> {
        Ok(self.has_errors)
    }

    #[getter]
    fn has_warnings(&self) -> PyResult<bool> {
        Ok(self.has_warnings)
    }

    #[getter]
    fn locked(&self) -> PyResult<bool> {
        Ok(self.locked)
    }

    #[getter]
    fn user(&self) -> PyResult<Option<String>> {
        Ok(self.user.clone())
    }

    #[getter]
    fn date_time_changed<'py>(&self, py: Python<'py>) -> PyResult<Option<Bound<'py, PyDateTime>>> {
        to_py_datetime_option(py, &self.date_time_changed)
    }

    #[getter]
    fn form_title(&self) -> PyResult<String> {
        Ok(self.form_title.clone())
    }

    #[getter]
    fn form_index(&self) -> PyResult<usize> {
        Ok(self.form_index)
    }

    #[getter]
    fn form_group(&self) -> PyResult<Option<String>> {
        Ok(self.form_group.clone())
    }

    #[getter]
    fn form_state(&self) -> PyResult<String> {
        Ok(self.form_state.clone())
    }

    #[getter]
    fn states(&self) -> PyResult<Option<Vec<State>>> {
        Ok(self.states.clone())
    }

    #[getter]
    fn lock_state(&self) -> PyResult<Option<LockState>> {
        Ok(self.lock_state.clone())
    }

    #[getter]
    fn categories(&self) -> PyResult<Option<Vec<Category>>> {
        Ok(self.categories.clone())
    }

    pub fn to_dict<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("name", &self.name)?;
        dict.set_item(
            "last_modified",
            to_py_datetime_option(py, &self.last_modified)?,
        )?;
        dict.set_item("who_last_modified_name", &self.who_last_modified_name)?;
        dict.set_item("who_last_modified_role", &self.who_last_modified_role)?;
        dict.set_item("when_created", self.when_created)?;
        dict.set_item("has_errors", self.has_errors)?;
        dict.set_item("has_warnings", self.has_warnings)?;
        dict.set_item("locked", self.locked)?;
        dict.set_item("user", &self.user)?;
        dict.set_item(
            "date_time_changed",
            to_py_datetime_option(py, &self.date_time_changed)?,
        )?;
        dict.set_item("form_title", &self.form_title)?;
        dict.set_item("form_index", self.form_index)?;
        dict.set_item("form_group", &self.form_group)?;
        dict.set_item("form_state", &self.form_state)?;

        let mut state_dicts = Vec::new();
        if let Some(states) = &self.states {
            for state in states {
                let state_dict = state.to_dict(py)?;
                state_dicts.push(state_dict);
            }
            dict.set_item("states", state_dicts)?;
        } else {
            dict.set_item("states", py.None())?;
        }

        if let Some(lock_state) = &self.lock_state {
            dict.set_item("lock_state", lock_state.to_dict(py)?)?;
        } else {
            dict.set_item("lock_state", py.None())?;
        }

        if let Some(categories) = &self.categories {
            let mut category_dicts = Vec::new();
            for category in categories {
                let category_dict = category.to_dict(py)?;
                category_dicts.push(category_dict);
            }
            dict.set_item("categories", category_dicts)?;
        } else {
            dict.set_item("categories", py.None())?;
        }

        Ok(dict)
    }
}

impl State {
    pub fn from_attributes(
        attrs: std::collections::HashMap<String, String>,
    ) -> Result<Self, crate::errors::Error> {
        let value = attrs.get("value").cloned().unwrap_or_default();
        let signer = attrs.get("signer").cloned().unwrap_or_default();
        let signer_unique_id = attrs.get("signerUniqueId").cloned().unwrap_or_default();

        let date_signed = if let Some(ds) = attrs.get("dateSigned") {
            if ds.is_empty() {
                None
            } else {
                parse_datetime_internal(ds).ok()
            }
        } else {
            None
        };

        Ok(State {
            value,
            signer,
            signer_unique_id,
            date_signed,
        })
    }
}

impl LockState {
    pub fn from_attributes(
        attrs: std::collections::HashMap<String, String>,
    ) -> Result<Self, crate::errors::Error> {
        let locked = attrs.get("locked").map(|s| s == "true").unwrap_or(false);
        let user = attrs.get("user").filter(|s| !s.is_empty()).cloned();
        let user_unique_id = attrs.get("userUniqueId").filter(|s| !s.is_empty()).cloned();

        let date_time_changed = if let Some(dtc) = attrs.get("dateTimeChanged") {
            if dtc.is_empty() {
                None
            } else {
                parse_datetime_internal(dtc).ok()
            }
        } else {
            None
        };

        Ok(LockState {
            locked,
            user,
            user_unique_id,
            date_time_changed,
        })
    }
}

impl Category {
    pub fn from_attributes(
        attrs: std::collections::HashMap<String, String>,
    ) -> Result<Self, crate::errors::Error> {
        let name = attrs.get("name").cloned().unwrap_or_default();
        let category_type = attrs.get("type").cloned().unwrap_or_default();
        let highest_index = attrs
            .get("highestIndex")
            .and_then(|s| s.parse().ok())
            .unwrap_or(0);

        Ok(Category {
            name,
            category_type,
            highest_index,
            fields: None,
        })
    }
}

impl Field {
    pub fn from_attributes(
        attrs: std::collections::HashMap<String, String>,
    ) -> Result<Self, crate::errors::Error> {
        let name = attrs.get("name").cloned().unwrap_or_default();
        let field_type = attrs.get("type").cloned().unwrap_or_default();
        let data_type = attrs.get("dataType").filter(|s| !s.is_empty()).cloned();
        let error_code = attrs.get("errorCode").cloned().unwrap_or_default();

        let when_created = if let Some(wc) = attrs.get("whenCreated") {
            if wc.is_empty() {
                None
            } else {
                Some(parse_datetime_internal(wc)?)
            }
        } else {
            None
        };

        let keep_history = attrs
            .get("keepHistory")
            .map(|s| s == "true")
            .unwrap_or(false);

        Ok(Field {
            name,
            field_type,
            data_type,
            error_code,
            when_created,
            keep_history,
            entries: None,
            comments: None,
        })
    }
}

impl Entry {
    pub fn from_attributes(
        attrs: std::collections::HashMap<String, String>,
    ) -> Result<Self, crate::errors::Error> {
        let entry_id = attrs
            .get("id")
            .or_else(|| attrs.get("entryId"))
            .cloned()
            .unwrap_or_default();

        let reviewed_by = attrs.get("reviewedBy").filter(|s| !s.is_empty()).cloned();
        let reviewed_by_unique_id = attrs
            .get("reviewedByUniqueId")
            .filter(|s| !s.is_empty())
            .cloned();

        let reviewed_by_when = if let Some(rbw) = attrs.get("reviewedByWhen") {
            if rbw.is_empty() {
                None
            } else {
                parse_datetime_internal(rbw).ok()
            }
        } else {
            None
        };

        Ok(Entry {
            entry_id,
            reviewed_by,
            reviewed_by_unique_id,
            reviewed_by_when,
            value: None,
            reason: None,
        })
    }
}

impl Value {
    pub fn from_attributes(
        attrs: std::collections::HashMap<String, String>,
    ) -> Result<Self, crate::errors::Error> {
        let by = attrs.get("by").cloned().unwrap_or_default();
        let by_unique_id = attrs.get("byUniqueId").filter(|s| !s.is_empty()).cloned();
        let role = attrs.get("role").cloned().unwrap_or_default();

        let when = if let Some(w) = attrs.get("when") {
            if w.is_empty() {
                None
            } else {
                Some(parse_datetime_internal(w)?)
            }
        } else {
            None
        };

        Ok(Value {
            by,
            by_unique_id,
            role,
            when,
            value: String::new(),
        })
    }
}

impl Reason {
    pub fn from_attributes(
        attrs: std::collections::HashMap<String, String>,
    ) -> Result<Self, crate::errors::Error> {
        let by = attrs.get("by").cloned().unwrap_or_default();
        let by_unique_id = attrs.get("byUniqueId").filter(|s| !s.is_empty()).cloned();
        let role = attrs.get("role").cloned().unwrap_or_default();

        let when = if let Some(w) = attrs.get("when") {
            if w.is_empty() {
                None
            } else {
                Some(parse_datetime_internal(w)?)
            }
        } else {
            None
        };

        Ok(Reason {
            by,
            by_unique_id,
            role,
            when,
            value: String::new(),
        })
    }
}
