use std::str::FromStr;

use serde::{Deserialize, Serialize};

use crate::{
    Country, TaxReductions, Taxes,
    constants::{DEFAULT_SE_TAX_REDUCTIONS, DEFAULT_SE_TAXES, REDUCED_SE_TAXES},
};

/// Local administrative unit parsing error
#[derive(Debug, thiserror::Error)]
pub enum LauParseError {
    #[error("Unsupported or invalid country code")]
    UnsupportedCountryCode,
    #[error("Invalid LAU code")]
    InvalidCode,
}

#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct LocalAdministrativeUnit {
    code: &'static str,
    name: &'static str,
    country: Country,
}

impl LocalAdministrativeUnit {
    pub fn eu_code(&self) -> String {
        format!("{}_{:0width$}", self.country.code(), self.code, width = 4)
    }

    pub fn local_code(&self) -> &str {
        self.code
    }

    pub fn info(&'static self) -> LocalAdministrativeUnitInfo {
        self.into()
    }

    pub fn name(&self) -> &str {
        self.name
    }

    pub fn country(&self) -> Country {
        self.country
    }

    pub fn get(country: Country, code: &str) -> Option<&'static Self> {
        match country {
            Country::SE => registry::SE.get(code),
        }
    }

    pub fn taxes(&self) -> Taxes {
        match self.country {
            Country::SE => {
                let code = self.code.parse().unwrap();
                if registry::SE_REDUCED_ENERGY_TAX_SUBDIVISIONS.contains(&code) {
                    REDUCED_SE_TAXES
                } else {
                    DEFAULT_SE_TAXES
                }
            }
        }
    }

    pub fn tax_reductions(&self) -> TaxReductions {
        match self.country {
            Country::SE => DEFAULT_SE_TAX_REDUCTIONS,
        }
    }

    pub fn from_eu_code(value: &str) -> Result<&'static Self, LauParseError> {
        let country: Country = value[0..2]
            .parse()
            .map_err(|_| LauParseError::UnsupportedCountryCode)?;
        let lau_code = &value[3..];
        match country {
            Country::SE => registry::SE.get(lau_code).ok_or(LauParseError::InvalidCode),
        }
    }
}

impl Serialize for LocalAdministrativeUnit {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.serialize_str(&self.eu_code())
    }
}

impl FromStr for LocalAdministrativeUnit {
    type Err = LauParseError;

    fn from_str(eu_lau_code: &str) -> Result<Self, Self::Err> {
        Self::from_eu_code(eu_lau_code).copied()
    }
}

impl<'de> Deserialize<'de> for &'static LocalAdministrativeUnit {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        LocalAdministrativeUnit::from_eu_code(&s).map_err(serde::de::Error::custom)
    }
}

impl<'de> Deserialize<'de> for LocalAdministrativeUnit {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let static_ref = <&'static LocalAdministrativeUnit>::deserialize(deserializer)?;
        Ok(*static_ref)
    }
}

#[derive(Debug, Clone, Copy, Serialize)]
pub struct LocalAdministrativeUnitInfo {
    pub code: &'static str,
    pub name: &'static str,
    pub country: Country,
    pub taxes: Taxes,
    pub tax_reductions: TaxReductions,
}

impl LocalAdministrativeUnitInfo {
    pub fn only_current(mut self) -> Self {
        self.taxes = self.taxes.with_current_only();
        self.tax_reductions = self.tax_reductions.with_current_only();
        self
    }
}

impl From<&'static LocalAdministrativeUnit> for LocalAdministrativeUnitInfo {
    fn from(value: &'static LocalAdministrativeUnit) -> Self {
        Self {
            code: value.local_code(),
            name: value.name(),
            country: value.country(),
            taxes: value.taxes(),
            tax_reductions: value.tax_reductions(),
        }
    }
}

pub(crate) mod registry {
    static SE_DATA: &str = include_str!("../data/EU-27-LAU-2024-NUTS-2024-SE.csv");
    use std::{
        collections::{HashMap, HashSet},
        str::FromStr,
        sync::LazyLock,
    };

    use crate::{Country, local_administrative_unit::LocalAdministrativeUnit};

    pub(crate) static SE: LazyLock<HashMap<&str, LocalAdministrativeUnit>> = LazyLock::new(|| {
        let mut map = HashMap::with_capacity(290);

        // Skip header line, then iterate over data lines
        for line in SE_DATA.lines().skip(1) {
            // Split on comma - no allocations, just borrows from SE_DATA
            let mut parts = line.split(',');

            let nuts_3_code = parts.next().unwrap();
            let lau_code = parts.next().unwrap();
            let name = parts.next().unwrap();
            let country = Country::from_str(&nuts_3_code[0..2]).unwrap();
            map.insert(
                lau_code,
                LocalAdministrativeUnit {
                    code: lau_code,
                    name,
                    country,
                },
            );
        }
        map
    });

    pub(crate) static SE_REDUCED_ENERGY_TAX_SUBDIVISIONS: LazyLock<HashSet<u16>> =
        LazyLock::new(|| {
            HashSet::from_iter([
                2506, 2505, 2326, 2403, 2582, 2305, 2425, 2523, 2583, 2361, 2510, 2514, 2584, 2309,
                2161, 2580, 2481, 2023, 2418, 2062, 2401, 2417, 2034, 2521, 2581, 2303, 2409, 2482,
                2283, 2422, 2421, 2313, 1737, 2480, 2462, 2404, 2460, 2260, 2321, 2463, 2039, 2560,
                2284, 2380, 2513, 2518,
            ])
        });
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::str::FromStr;

    #[test]
    fn test_eu_code_formatting() {
        // Test that EU code is properly formatted with country code and zero-padded LAU code
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        assert_eq!(lau.eu_code(), "SE_0180");
    }

    #[test]
    fn test_local_code() {
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        assert_eq!(lau.local_code(), "0180");
    }

    #[test]
    fn test_name() {
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        assert_eq!(lau.name(), "Stockholm");
    }

    #[test]
    fn test_country() {
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        assert_eq!(lau.country(), Country::SE);
    }

    #[test]
    fn test_get_valid_code() {
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180");
        assert!(lau.is_some());
    }

    #[test]
    fn test_get_invalid_code() {
        let lau = LocalAdministrativeUnit::get(Country::SE, "9999");
        assert!(lau.is_none());
    }

    #[test]
    fn test_taxes_default() {
        // Test a municipality that should have default taxes (e.g., Stockholm)
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        let taxes = lau.taxes();
        assert_eq!(taxes, DEFAULT_SE_TAXES);
    }

    #[test]
    fn test_taxes_reduced() {
        // Test a municipality that should have reduced energy tax (e.g., 2506)
        let lau = LocalAdministrativeUnit::get(Country::SE, "2506").unwrap();
        let taxes = lau.taxes();
        assert_eq!(taxes, REDUCED_SE_TAXES);
        assert_ne!(taxes, DEFAULT_SE_TAXES);
    }

    #[test]
    fn test_tax_reductions() {
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        let tax_reductions = lau.tax_reductions();
        assert_eq!(tax_reductions, DEFAULT_SE_TAX_REDUCTIONS);
    }

    #[test]
    fn test_from_eu_code_valid() {
        let result = LocalAdministrativeUnit::from_eu_code("SE_0180");
        assert!(result.is_ok());
        let lau = result.unwrap();
        assert_eq!(lau.local_code(), "0180");
        assert_eq!(lau.name(), "Stockholm");
    }

    #[test]
    fn test_from_eu_code_invalid_country() {
        let result = LocalAdministrativeUnit::from_eu_code("XX_0180");
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            LauParseError::UnsupportedCountryCode
        ));
    }

    #[test]
    fn test_from_eu_code_invalid_lau() {
        let result = LocalAdministrativeUnit::from_eu_code("SE_9999");
        assert!(result.is_err());
        assert!(matches!(result.unwrap_err(), LauParseError::InvalidCode));
    }

    #[test]
    fn test_from_str_valid() {
        let result = LocalAdministrativeUnit::from_str("SE_0180");
        assert!(result.is_ok());
        let lau = result.unwrap();
        assert_eq!(lau.local_code(), "0180");
    }

    #[test]
    fn test_from_str_invalid() {
        let result = LocalAdministrativeUnit::from_str("INVALID");
        assert!(result.is_err());
    }

    #[test]
    fn test_serialize() {
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        let serialized = serde_json::to_string(lau).unwrap();
        assert_eq!(serialized, "\"SE_0180\"");
    }

    #[test]
    fn test_deserialize_static_ref() {
        let json = "\"SE_0180\"";
        let lau: &'static LocalAdministrativeUnit = serde_json::from_str(json).unwrap();
        assert_eq!(lau.local_code(), "0180");
        assert_eq!(lau.name(), "Stockholm");
    }

    #[test]
    fn test_deserialize_owned() {
        let json = "\"SE_0180\"";
        let lau: LocalAdministrativeUnit = serde_json::from_str(json).unwrap();
        assert_eq!(lau.local_code(), "0180");
        assert_eq!(lau.name(), "Stockholm");
    }

    #[test]
    fn test_deserialize_invalid() {
        let json = "\"SE_9999\"";
        let result: Result<&'static LocalAdministrativeUnit, _> = serde_json::from_str(json);
        assert!(result.is_err());
    }

    #[test]
    fn test_info_conversion() {
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        let info = lau.info();
        assert_eq!(info.code, "0180");
        assert_eq!(info.name, "Stockholm");
        assert_eq!(info.country, Country::SE);
        assert_eq!(info.taxes, lau.taxes());
        assert_eq!(info.tax_reductions, lau.tax_reductions());
    }

    #[test]
    fn test_lau_ordering() {
        let lau1 = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        let lau2 = LocalAdministrativeUnit::get(Country::SE, "1480").unwrap();
        // Test that LocalAdministrativeUnit implements Ord correctly
        assert!(lau1 < lau2 || lau1 > lau2 || lau1 == lau1);
    }

    #[test]
    fn test_lau_hash() {
        use std::collections::HashSet;
        let lau1 = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        let lau2 = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();

        let mut set = HashSet::new();
        set.insert(lau1);
        // Same LAU should be considered equal
        assert!(set.contains(lau2));
    }

    #[test]
    fn test_registry_loaded() {
        // Test that the Swedish registry is properly loaded
        let se_registry = &*registry::SE;
        assert!(!se_registry.is_empty());
        // Should contain Stockholm
        assert!(se_registry.contains_key("0180"));
    }

    #[test]
    fn test_reduced_energy_tax_subdivisions() {
        // Test that the reduced energy tax subdivisions set is properly loaded
        let reduced = &*registry::SE_REDUCED_ENERGY_TAX_SUBDIVISIONS;
        assert!(!reduced.is_empty());
        // Test a known reduced tax subdivision
        assert!(reduced.contains(&2506));
        // Test that Stockholm (180) is not in the reduced tax list
        assert!(!reduced.contains(&180));
    }

    #[test]
    fn test_roundtrip_serialization() {
        let lau = LocalAdministrativeUnit::get(Country::SE, "0180").unwrap();
        let serialized = serde_json::to_string(lau).unwrap();
        let deserialized: LocalAdministrativeUnit = serde_json::from_str(&serialized).unwrap();
        assert_eq!(lau.local_code(), deserialized.local_code());
        assert_eq!(lau.name(), deserialized.name());
        assert_eq!(lau.country(), deserialized.country());
    }
}
