use std::collections::HashSet;
use std::sync::Arc;

use serde_json::{Map, Value};

use crate::scrape::athena::{AthenaInsertStatus, DexterAthena};

use super::mapping::NormalizedTaxBatch;

#[derive(Debug, Clone, Default)]
pub struct TaxInsertStats {
    pub tax_types_inserted: u64,
    pub tax_types_duplicates: u64,
    pub tax_types_failed: u64,
    pub tax_rates_inserted: u64,
    pub tax_rates_duplicates: u64,
    pub tax_rates_failed: u64,
    pub tax_rates_skipped_missing_type: u64,
    pub tax_due_dates_inserted: u64,
    pub tax_due_dates_duplicates: u64,
    pub tax_due_dates_failed: u64,
    pub tax_due_dates_skipped_missing_type: u64,
}

pub struct TaxDatabaseInserter {
    dexter: Arc<DexterAthena>,
    max_workers: usize,
}

impl TaxDatabaseInserter {
    pub fn new(dexter: Arc<DexterAthena>, max_workers: usize) -> Self {
        Self {
            dexter,
            max_workers,
        }
    }

    pub async fn insert_batch(&self, batch: NormalizedTaxBatch) -> TaxInsertStats {
        let mut stats = TaxInsertStats::default();
        let mut seen_type_keys = HashSet::new();
        let mut seen_rate_keys = HashSet::new();
        let mut seen_due_keys = HashSet::new();
        let mut ready_tax_type_ids = HashSet::new();

        for row in batch.tax_types {
            let Some(dedupe_key) = tax_type_dedupe_key(&row) else {
                stats.tax_types_failed += 1;
                continue;
            };
            if !seen_type_keys.insert(dedupe_key) {
                continue;
            }

            let Some(tax_type_id) = row
                .get("tax_type_id")
                .and_then(|v| v.as_str())
                .map(str::to_string)
            else {
                stats.tax_types_failed += 1;
                continue;
            };

            let payload = row_to_gateway_payload(&row);
            let (status, detail) = self
                .dexter
                .insert_row_with_retry_resilient("tax_types", payload, self.max_workers)
                .await;
            match status {
                AthenaInsertStatus::Ok => {
                    stats.tax_types_inserted += 1;
                    ready_tax_type_ids.insert(tax_type_id);
                }
                AthenaInsertStatus::Duplicate => {
                    stats.tax_types_duplicates += 1;
                    ready_tax_type_ids.insert(tax_type_id);
                }
                AthenaInsertStatus::Error | AthenaInsertStatus::Exception => {
                    stats.tax_types_failed += 1;
                    tracing::error!("Failed tax_types insert: {:?}", detail);
                }
            }
        }

        for row in batch.tax_rates {
            let Some(dedupe_key) = tax_rate_dedupe_key(&row) else {
                stats.tax_rates_failed += 1;
                continue;
            };
            if !seen_rate_keys.insert(dedupe_key) {
                continue;
            }

            let Some(tax_type_id) = row.get("tax_type_id").and_then(|v| v.as_str()) else {
                stats.tax_rates_failed += 1;
                continue;
            };
            if !ready_tax_type_ids.contains(tax_type_id) {
                stats.tax_rates_skipped_missing_type += 1;
                continue;
            }

            let payload = row_to_gateway_payload(&row);
            let (status, detail) = self
                .dexter
                .insert_row_with_retry_resilient("tax_rates", payload, self.max_workers)
                .await;
            match status {
                AthenaInsertStatus::Ok => {
                    stats.tax_rates_inserted += 1;
                }
                AthenaInsertStatus::Duplicate => {
                    stats.tax_rates_duplicates += 1;
                }
                AthenaInsertStatus::Error | AthenaInsertStatus::Exception => {
                    stats.tax_rates_failed += 1;
                    tracing::error!("Failed tax_rates insert: {:?}", detail);
                }
            }
        }

        for row in batch.tax_due_dates {
            let Some(dedupe_key) = tax_due_date_dedupe_key(&row) else {
                stats.tax_due_dates_failed += 1;
                continue;
            };
            if !seen_due_keys.insert(dedupe_key) {
                continue;
            }

            let Some(tax_type_id) = row.get("tax_type_id").and_then(|v| v.as_str()) else {
                stats.tax_due_dates_failed += 1;
                continue;
            };
            if !ready_tax_type_ids.contains(tax_type_id) {
                stats.tax_due_dates_skipped_missing_type += 1;
                continue;
            }

            let payload = row_to_gateway_payload(&row);
            let (status, detail) = self
                .dexter
                .insert_row_with_retry_resilient("tax_due_dates", payload, self.max_workers)
                .await;
            match status {
                AthenaInsertStatus::Ok => {
                    stats.tax_due_dates_inserted += 1;
                }
                AthenaInsertStatus::Duplicate => {
                    stats.tax_due_dates_duplicates += 1;
                }
                AthenaInsertStatus::Error | AthenaInsertStatus::Exception => {
                    stats.tax_due_dates_failed += 1;
                    tracing::error!("Failed tax_due_dates insert: {:?}", detail);
                }
            }
        }

        stats
    }
}

pub fn row_to_gateway_payload(row: &Map<String, Value>) -> Value {
    let mut out = Map::new();
    for (k, v) in row {
        if v.is_null() {
            continue;
        }
        if k == "id" || k == "created_at" {
            continue;
        }
        out.insert(k.clone(), v.clone());
    }
    Value::Object(out)
}

pub fn tax_type_dedupe_key(row: &Map<String, Value>) -> Option<String> {
    let country_code = get_key_part(row, "country_code");
    let tax_year = get_key_part(row, "tax_year");
    let obligation_code = get_key_part(row, "obligation_code");
    if country_code.is_empty() || tax_year.is_empty() || obligation_code.is_empty() {
        return None;
    }
    Some(format!(
        "{}|{}|{}",
        country_code.to_uppercase(),
        tax_year,
        obligation_code.to_lowercase()
    ))
}

pub fn tax_rate_dedupe_key(row: &Map<String, Value>) -> Option<String> {
    let country_code = get_key_part(row, "country_code");
    let tax_year = get_key_part(row, "tax_year");
    let tax_type_id = get_key_part(row, "tax_type_id");
    let rate_kind = get_key_part(row, "rate_kind");
    if country_code.is_empty()
        || tax_year.is_empty()
        || tax_type_id.is_empty()
        || rate_kind.is_empty()
    {
        return None;
    }
    Some(format!(
        "{}|{}|{}|{}|{}|{}|{}|{}|{}",
        country_code.to_uppercase(),
        tax_year,
        tax_type_id,
        rate_kind,
        get_key_part(row, "effective_from"),
        get_key_part(row, "effective_to"),
        get_key_part(row, "base_amount_min"),
        get_key_part(row, "base_amount_max"),
        get_key_part(row, "rate_percent")
    ))
}

pub fn tax_due_date_dedupe_key(row: &Map<String, Value>) -> Option<String> {
    let country_code = get_key_part(row, "country_code");
    let tax_year = get_key_part(row, "tax_year");
    let tax_type_id = get_key_part(row, "tax_type_id");
    let period_code = get_key_part(row, "period_code");
    if country_code.is_empty()
        || tax_year.is_empty()
        || tax_type_id.is_empty()
        || period_code.is_empty()
    {
        return None;
    }
    let due_value = {
        let due_date = get_key_part(row, "due_date");
        let due_rule = get_key_part(row, "due_date_rule");
        if !due_date.is_empty() {
            due_date
        } else {
            due_rule
        }
    };
    if due_value.is_empty() {
        return None;
    }
    Some(format!(
        "{}|{}|{}|{}|{}",
        country_code.to_uppercase(),
        tax_year,
        tax_type_id,
        period_code.to_lowercase(),
        due_value
    ))
}

fn get_key_part(row: &Map<String, Value>, key: &str) -> String {
    row.get(key)
        .map(value_to_string)
        .unwrap_or_default()
        .trim()
        .to_string()
}

fn value_to_string(v: &Value) -> String {
    match v {
        Value::Null => String::new(),
        Value::String(s) => s.clone(),
        other => other.to_string(),
    }
}

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

    #[test]
    fn tax_type_dedupe_key_uses_country_year_obligation() {
        let mut row = Map::new();
        row.insert("country_code".to_string(), json!("nl"));
        row.insert("tax_year".to_string(), json!(2026));
        row.insert("obligation_code".to_string(), json!("vat_monthly"));
        let key = tax_type_dedupe_key(&row).expect("key");
        assert_eq!(key, "NL|2026|vat_monthly");
    }

    #[test]
    fn tax_rate_dedupe_key_requires_core_fields() {
        let mut row = Map::new();
        row.insert("country_code".to_string(), json!("NL"));
        row.insert("tax_year".to_string(), json!(2026));
        row.insert("tax_type_id".to_string(), json!("a"));
        row.insert("rate_kind".to_string(), json!("flat"));
        let key = tax_rate_dedupe_key(&row).expect("key");
        assert!(key.contains("NL|2026|a|flat"));
    }

    #[test]
    fn tax_due_date_dedupe_key_requires_due_date_or_rule() {
        let mut row = Map::new();
        row.insert("country_code".to_string(), json!("NL"));
        row.insert("tax_year".to_string(), json!(2026));
        row.insert("tax_type_id".to_string(), json!("a"));
        row.insert("period_code".to_string(), json!("annual"));
        assert!(tax_due_date_dedupe_key(&row).is_none());
        row.insert("due_date_rule".to_string(), json!("31 March"));
        assert!(tax_due_date_dedupe_key(&row).is_some());
    }
}
