use anyhow::{anyhow, bail, Context, Result};
use chrono::Utc;
use serde::Deserialize;
use serde_json::{Map, Number, Value};
use sha2::{Digest, Sha256};
use uuid::Uuid;

#[derive(Debug, Clone, Default)]
pub struct NormalizedTaxBatch {
    pub tax_types: Vec<Map<String, Value>>,
    pub tax_rates: Vec<Map<String, Value>>,
    pub tax_due_dates: Vec<Map<String, Value>>,
}

impl NormalizedTaxBatch {
    pub fn is_empty(&self) -> bool {
        self.tax_types.is_empty() && self.tax_rates.is_empty() && self.tax_due_dates.is_empty()
    }
}

#[derive(Debug, Clone, Deserialize)]
pub struct TaxInputEnvelope {
    #[serde(default)]
    pub country_code: Option<String>,
    #[serde(default)]
    pub tax_year: Option<i32>,
    #[serde(default)]
    pub records: Option<Vec<TaxEntryInput>>,
    #[serde(default)]
    pub tax_types: Option<Vec<TaxEntryInput>>,
    #[serde(default)]
    pub items: Option<Vec<TaxEntryInput>>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct TaxEntryInput {
    #[serde(default)]
    pub country_code: Option<String>,
    #[serde(default)]
    pub tax_year: Option<i32>,
    #[serde(default)]
    pub tax_category: Option<String>,
    #[serde(default)]
    pub obligation_code: Option<String>,
    #[serde(default)]
    pub name: Option<String>,
    #[serde(default)]
    pub description: Option<String>,
    #[serde(default)]
    pub metadata: Option<Value>,
    #[serde(default)]
    pub source_name: Option<String>,
    #[serde(default)]
    pub source_url: Option<String>,
    #[serde(default)]
    pub rates: Vec<TaxRateInput>,
    #[serde(default)]
    pub due_dates: Vec<TaxDueDateInput>,
    #[serde(default)]
    pub rate: Option<TaxRateInput>,
    #[serde(default)]
    pub due_date: Option<TaxDueDateInput>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct TaxRateInput {
    #[serde(default)]
    pub rate_kind: Option<String>,
    #[serde(default)]
    pub rate_percent: Option<f64>,
    #[serde(default)]
    pub base_amount_min: Option<f64>,
    #[serde(default)]
    pub base_amount_max: Option<f64>,
    #[serde(default)]
    pub effective_from: Option<String>,
    #[serde(default)]
    pub effective_to: Option<String>,
    #[serde(default)]
    pub rate_brackets: Option<Value>,
    #[serde(default)]
    pub source_name: Option<String>,
    #[serde(default)]
    pub source_url: Option<String>,
    #[serde(default)]
    pub harvested_at: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct TaxDueDateInput {
    #[serde(default)]
    pub period_code: Option<String>,
    #[serde(default)]
    pub due_date: Option<String>,
    #[serde(default)]
    pub due_date_rule: Option<String>,
    #[serde(default)]
    pub notes: Option<String>,
    #[serde(default)]
    pub source_name: Option<String>,
    #[serde(default)]
    pub source_url: Option<String>,
    #[serde(default)]
    pub harvested_at: Option<String>,
}

pub fn parse_tax_input_document(value: Value) -> Result<Vec<TaxEntryInput>> {
    match value {
        Value::Array(_) => serde_json::from_value(value).context("parse top-level array"),
        Value::Object(map) => {
            if map.contains_key("records")
                || map.contains_key("tax_types")
                || map.contains_key("items")
            {
                let env: TaxInputEnvelope = serde_json::from_value(Value::Object(map))
                    .context("parse wrapped tax input")?;
                let mut entries = if let Some(v) = env.records {
                    v
                } else if let Some(v) = env.tax_types {
                    v
                } else {
                    env.items.unwrap_or_default()
                };
                for entry in &mut entries {
                    if entry.country_code.is_none() {
                        entry.country_code = env.country_code.clone();
                    }
                    if entry.tax_year.is_none() {
                        entry.tax_year = env.tax_year;
                    }
                }
                Ok(entries)
            } else {
                let single: TaxEntryInput =
                    serde_json::from_value(Value::Object(map)).context("parse single tax entry")?;
                Ok(vec![single])
            }
        }
        other => bail!(
            "tax input must be a JSON object or array, got {}",
            other
                .as_str()
                .map(|_| "string")
                .unwrap_or("unsupported type")
        ),
    }
}

pub fn normalize_tax_entries(
    entries: Vec<TaxEntryInput>,
    default_country_code: &str,
    default_tax_year: i32,
) -> Result<NormalizedTaxBatch> {
    let fallback_country = normalize_country_code(default_country_code)
        .ok_or_else(|| anyhow!("invalid default country code: {}", default_country_code))?;
    let fallback_year = validate_tax_year(default_tax_year)?;
    let mut out = NormalizedTaxBatch::default();

    for (idx, entry) in entries.into_iter().enumerate() {
        let country_code =
            normalize_country_code(entry.country_code.as_deref().unwrap_or(&fallback_country))
                .ok_or_else(|| anyhow!("entry {} has invalid country code", idx + 1))?;
        let tax_year = validate_tax_year(entry.tax_year.unwrap_or(fallback_year))
            .with_context(|| format!("entry {} has invalid tax year", idx + 1))?;
        let raw_category = entry
            .tax_category
            .as_deref()
            .ok_or_else(|| anyhow!("entry {} missing tax_category", idx + 1))?;
        let tax_category = normalize_tax_category(raw_category).ok_or_else(|| {
            anyhow!(
                "entry {} has unsupported tax_category '{}'",
                idx + 1,
                raw_category
            )
        })?;

        let obligation_code_raw = entry
            .obligation_code
            .unwrap_or_else(|| default_obligation_code(tax_category));
        let obligation_code = normalize_code(&obligation_code_raw)
            .ok_or_else(|| anyhow!("entry {} has invalid obligation_code", idx + 1))?;

        let tax_type_id =
            deterministic_tax_type_id(&country_code, tax_year, tax_category, &obligation_code)
                .to_string();
        let mut type_row = Map::new();
        type_row.insert(
            "country_code".to_string(),
            Value::String(country_code.clone()),
        );
        type_row.insert(
            "tax_year".to_string(),
            Value::Number(Number::from(i64::from(tax_year))),
        );
        type_row.insert(
            "tax_category".to_string(),
            Value::String(tax_category.to_string()),
        );
        type_row.insert(
            "obligation_code".to_string(),
            Value::String(obligation_code.clone()),
        );
        type_row.insert(
            "tax_type_id".to_string(),
            Value::String(tax_type_id.clone()),
        );
        insert_opt_text(&mut type_row, "name", entry.name.as_deref());
        insert_opt_text(&mut type_row, "description", entry.description.as_deref());
        if let Some(metadata) = entry.metadata.clone().filter(|m| !m.is_null()) {
            type_row.insert("metadata".to_string(), metadata);
        }
        out.tax_types.push(type_row);

        let mut rates = entry.rates;
        if let Some(rate) = entry.rate {
            rates.push(rate);
        }

        for rate in rates {
            let rate_kind = normalize_rate_kind(rate.rate_kind.as_deref().unwrap_or("flat"))
                .ok_or_else(|| anyhow!("entry {} has invalid rate_kind", idx + 1))?;
            let mut rate_row = Map::new();
            rate_row.insert(
                "country_code".to_string(),
                Value::String(country_code.clone()),
            );
            rate_row.insert(
                "tax_year".to_string(),
                Value::Number(Number::from(i64::from(tax_year))),
            );
            rate_row.insert(
                "tax_type_id".to_string(),
                Value::String(tax_type_id.clone()),
            );
            rate_row.insert(
                "rate_kind".to_string(),
                Value::String(rate_kind.to_string()),
            );
            insert_opt_f64(&mut rate_row, "rate_percent", rate.rate_percent);
            insert_opt_f64(&mut rate_row, "base_amount_min", rate.base_amount_min);
            insert_opt_f64(&mut rate_row, "base_amount_max", rate.base_amount_max);
            insert_opt_text(
                &mut rate_row,
                "effective_from",
                rate.effective_from.as_deref(),
            );
            insert_opt_text(&mut rate_row, "effective_to", rate.effective_to.as_deref());
            if let Some(brackets) = rate.rate_brackets.clone().filter(|v| !v.is_null()) {
                rate_row.insert("rate_brackets".to_string(), brackets);
            }
            insert_opt_text(
                &mut rate_row,
                "source_name",
                rate.source_name.as_deref().or(entry.source_name.as_deref()),
            );
            insert_opt_text(
                &mut rate_row,
                "source_url",
                rate.source_url.as_deref().or(entry.source_url.as_deref()),
            );
            insert_opt_text(
                &mut rate_row,
                "harvested_at",
                rate.harvested_at
                    .as_deref()
                    .or(Some(Utc::now().to_rfc3339().as_str())),
            );
            out.tax_rates.push(rate_row);
        }

        let mut due_dates = entry.due_dates;
        if let Some(due_date) = entry.due_date {
            due_dates.push(due_date);
        }

        for due in due_dates {
            let period_code = normalize_code(due.period_code.as_deref().unwrap_or("annual"))
                .unwrap_or_else(|| "annual".to_string());
            let due_date = sanitize_optional_text(due.due_date.as_deref());
            let due_date_rule = sanitize_optional_text(due.due_date_rule.as_deref());
            if due_date.is_none() && due_date_rule.is_none() {
                continue;
            }
            let mut due_row = Map::new();
            due_row.insert(
                "country_code".to_string(),
                Value::String(country_code.clone()),
            );
            due_row.insert(
                "tax_year".to_string(),
                Value::Number(Number::from(i64::from(tax_year))),
            );
            due_row.insert(
                "tax_type_id".to_string(),
                Value::String(tax_type_id.clone()),
            );
            due_row.insert("period_code".to_string(), Value::String(period_code));
            insert_opt_text(&mut due_row, "due_date", due_date.as_deref());
            insert_opt_text(&mut due_row, "due_date_rule", due_date_rule.as_deref());
            insert_opt_text(&mut due_row, "notes", due.notes.as_deref());
            insert_opt_text(
                &mut due_row,
                "source_name",
                due.source_name.as_deref().or(entry.source_name.as_deref()),
            );
            insert_opt_text(
                &mut due_row,
                "source_url",
                due.source_url.as_deref().or(entry.source_url.as_deref()),
            );
            insert_opt_text(
                &mut due_row,
                "harvested_at",
                due.harvested_at
                    .as_deref()
                    .or(Some(Utc::now().to_rfc3339().as_str())),
            );
            out.tax_due_dates.push(due_row);
        }
    }

    Ok(out)
}

pub fn deterministic_tax_type_id(
    country_code: &str,
    tax_year: i32,
    tax_category: &str,
    obligation_code: &str,
) -> Uuid {
    let seed = format!(
        "{}|{}|{}|{}",
        country_code.trim().to_uppercase(),
        tax_year,
        tax_category.trim().to_uppercase(),
        obligation_code.trim().to_lowercase()
    );
    let digest = Sha256::digest(seed.as_bytes());
    let mut bytes = [0u8; 16];
    bytes.copy_from_slice(&digest[..16]);
    bytes[6] = (bytes[6] & 0x0f) | 0x40;
    bytes[8] = (bytes[8] & 0x3f) | 0x80;
    Uuid::from_bytes(bytes)
}

pub fn normalize_tax_category(raw: &str) -> Option<&'static str> {
    let normalized = raw.trim().to_lowercase().replace(['-', ' '], "_");
    match normalized.as_str() {
        "cit" | "corporate_income_tax" | "corporate_tax" => Some("CIT"),
        "pit" | "personal_income_tax" | "income_tax_personal" => Some("PIT"),
        "vat" | "value_added_tax" | "value_added_tax_vat" => Some("VAT"),
        "wht" | "withholding_tax" => Some("WHT"),
        "cgt" | "capital_gains_tax" => Some("CGT"),
        "net_wealth_tax" | "net_worth_tax" | "wealth_tax" | "net_wealth_worth_tax" => {
            Some("NET_WEALTH")
        }
        "inheritance_and_gift_tax" | "inheritance_gift_tax" | "inheritance_tax" | "gift_tax" => {
            Some("INHERITANCE_GIFT")
        }
        _ => None,
    }
}

fn normalize_rate_kind(raw: &str) -> Option<&'static str> {
    let normalized = raw.trim().to_lowercase().replace(['-', ' '], "_");
    match normalized.as_str() {
        "flat" => Some("flat"),
        "bracket" | "tiered" => Some("bracket"),
        "progressive" => Some("progressive"),
        "exemption" | "zero" | "none" => Some("exemption"),
        _ => None,
    }
}

fn default_obligation_code(tax_category: &str) -> String {
    format!("{}_standard", tax_category.to_lowercase())
}

fn normalize_country_code(raw: &str) -> Option<String> {
    let cc = raw.trim().to_uppercase();
    if cc.len() == 2 && cc.chars().all(|c| c.is_ascii_alphabetic()) {
        Some(cc)
    } else {
        None
    }
}

fn validate_tax_year(year: i32) -> Result<i32> {
    if (1900..=2500).contains(&year) {
        Ok(year)
    } else {
        bail!("tax_year must be between 1900 and 2500")
    }
}

fn normalize_code(raw: &str) -> Option<String> {
    let mut out = String::with_capacity(raw.len());
    let mut prev_underscore = false;
    for c in raw.trim().chars() {
        let mapped = if c.is_ascii_alphanumeric() {
            c.to_ascii_lowercase()
        } else {
            '_'
        };
        if mapped == '_' {
            if prev_underscore {
                continue;
            }
            prev_underscore = true;
            out.push(mapped);
        } else {
            prev_underscore = false;
            out.push(mapped);
        }
    }
    let out = out.trim_matches('_').to_string();
    if out.is_empty() {
        None
    } else {
        Some(out)
    }
}

fn sanitize_optional_text(raw: Option<&str>) -> Option<String> {
    raw.map(str::trim)
        .filter(|s| !s.is_empty())
        .map(str::to_string)
}

fn insert_opt_text(row: &mut Map<String, Value>, key: &str, value: Option<&str>) {
    if let Some(v) = sanitize_optional_text(value) {
        row.insert(key.to_string(), Value::String(v));
    }
}

fn insert_opt_f64(row: &mut Map<String, Value>, key: &str, value: Option<f64>) {
    if let Some(v) = value.filter(|x| x.is_finite()) {
        if let Some(num) = Number::from_f64(v) {
            row.insert(key.to_string(), Value::Number(num));
        }
    }
}

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

    #[test]
    fn normalize_tax_category_aliases() {
        assert_eq!(normalize_tax_category("cit"), Some("CIT"));
        assert_eq!(normalize_tax_category("corporate income tax"), Some("CIT"));
        assert_eq!(normalize_tax_category("withholding_tax"), Some("WHT"));
        assert_eq!(
            normalize_tax_category("inheritance and gift tax"),
            Some("INHERITANCE_GIFT")
        );
    }

    #[test]
    fn deterministic_tax_type_id_is_stable() {
        let one = deterministic_tax_type_id("NL", 2026, "VAT", "vat_monthly");
        let two = deterministic_tax_type_id("NL", 2026, "VAT", "vat_monthly");
        assert_eq!(one, two);
    }

    #[test]
    fn normalize_tax_entries_builds_related_rows() {
        let entry = TaxEntryInput {
            country_code: Some("NL".to_string()),
            tax_year: Some(2026),
            tax_category: Some("VAT".to_string()),
            obligation_code: Some("vat-monthly".to_string()),
            name: Some("VAT monthly return".to_string()),
            description: None,
            metadata: None,
            source_name: Some("manual".to_string()),
            source_url: Some("https://example.test/vat".to_string()),
            rates: vec![TaxRateInput {
                rate_kind: Some("flat".to_string()),
                rate_percent: Some(21.0),
                base_amount_min: None,
                base_amount_max: None,
                effective_from: Some("2026-01-01".to_string()),
                effective_to: None,
                rate_brackets: None,
                source_name: None,
                source_url: None,
                harvested_at: None,
            }],
            due_dates: vec![TaxDueDateInput {
                period_code: Some("month_01".to_string()),
                due_date: Some("2026-02-28".to_string()),
                due_date_rule: None,
                notes: None,
                source_name: None,
                source_url: None,
                harvested_at: None,
            }],
            rate: None,
            due_date: None,
        };

        let batch = normalize_tax_entries(vec![entry], "NL", 2026).expect("normalize");
        assert_eq!(batch.tax_types.len(), 1);
        assert_eq!(batch.tax_rates.len(), 1);
        assert_eq!(batch.tax_due_dates.len(), 1);
    }
}
