//! Maps Northdata / KVK / NL rows to `global_companies_registry` and `ee_companies`.
//! Mirrors `scripts/scrape_global_registry.py` and NL `map_to_global_company`.

use std::collections::HashMap;

use once_cell::sync::Lazy;
use percent_encoding::percent_decode_str;
use regex::Regex;
use serde_json::{Map, Number, Value};
use sha2::{Digest, Sha256};
use url::Url;

/// Columns allowed on `public.global_companies_registry` (Python `GLOBAL_REGISTRY_KEYS`).
pub static GLOBAL_REGISTRY_KEYS: &[&str] = &[
    "name",
    "company_number",
    "rights_form",
    "country_code",
    "status",
    "last_update",
    "description",
    "region",
    "rights_form_code",
    "state",
    "name_english",
    "language",
    "hash",
    "address",
    "global_company_id",
    "start_date",
    "legal_persons",
    "contact_data",
    "activities",
    "activity_code_table",
    "establishment_date",
    "establishment_type",
    "entity_type",
    "vat_id",
    "email",
    "iban",
    "phone",
    "postal_code",
    "house_number",
    "province",
    "city",
    "street",
    "country",
    "website",
];

static GLOBAL_KEY_SET: Lazy<std::collections::HashSet<&'static str>> =
    Lazy::new(|| GLOBAL_REGISTRY_KEYS.iter().copied().collect());

static EE_COMPANIES_KEYS: &[&str] = &[
    "registrikood",
    "name",
    "legal_form",
    "status",
    "location",
    "vat_id",
    "register_reference",
    "source_url",
];

static EE_KEY_SET: Lazy<std::collections::HashSet<&'static str>> =
    Lazy::new(|| EE_COMPANIES_KEYS.iter().copied().collect());

static RE_EE_SEGMENT: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)^RIK\s*(\d{7,9})\s*$").unwrap());
static RE_EE_DIGITS: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\d{7,9}$").unwrap());

pub fn filter_global_payload(d: &Map<String, Value>) -> Map<String, Value> {
    let mut out = Map::new();
    for (k, v) in d {
        if v.is_null() {
            continue;
        }
        if GLOBAL_KEY_SET.contains(k.as_str()) {
            out.insert(k.clone(), v.clone());
        }
    }
    out.remove("global_company_id");
    out
}

pub fn filter_ee_payload(d: &Map<String, Value>) -> Map<String, Value> {
    let mut out = Map::new();
    for (k, v) in d {
        if v.is_null() {
            continue;
        }
        if EE_KEY_SET.contains(k.as_str()) {
            out.insert(k.clone(), v.clone());
        }
    }
    out
}

pub fn parse_ee_registrikood_from_segment(segment: &str) -> Option<String> {
    let s = segment.trim();
    if s.is_empty() {
        return None;
    }
    if let Some(c) = RE_EE_SEGMENT.captures(s) {
        return Some(c.get(1)?.as_str().to_string());
    }
    if RE_EE_DIGITS.is_match(s) {
        return Some(s.to_string());
    }
    None
}

static RE_EE_OU: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)\bOÜ\b|Osaühing").unwrap());
static RE_EE_AS: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)\bAS\b|Aktsiaselts").unwrap());
static RE_EE_FIE: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)\bFIE\b").unwrap());
static RE_EE_MTU: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)\bMTÜ\b").unwrap());
static RE_EE_UU: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)\bUÜ\b").unwrap());
static RE_EE_TU: Lazy<Regex> = Lazy::new(|| Regex::new(r"(?i)\bTÜ\b|Täisühing").unwrap());

pub fn infer_ee_legal_form(name: &str) -> Option<String> {
    if name.is_empty() {
        return None;
    }
    if RE_EE_OU.is_match(name) {
        return Some("OÜ".to_string());
    }
    if RE_EE_AS.is_match(name) {
        return Some("AS".to_string());
    }
    if RE_EE_FIE.is_match(name) {
        return Some("FIE".to_string());
    }
    if RE_EE_MTU.is_match(name) {
        return Some("MTÜ".to_string());
    }
    if RE_EE_UU.is_match(name) {
        return Some("UÜ".to_string());
    }
    if RE_EE_TU.is_match(name) {
        return Some("TÜ".to_string());
    }
    None
}

pub fn map_global_row_to_ee_company(row: &Map<String, Value>) -> Option<Map<String, Value>> {
    let cc = row
        .get("country_code")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_uppercase();
    if cc != "EE" {
        return None;
    }
    let segment = row
        .get("company_number")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .trim()
        .to_string();
    let rk = parse_ee_registrikood_from_segment(&segment)?;
    let name = row
        .get("name")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .trim();
    if name.is_empty() {
        return None;
    }
    let mut ee: Map<String, Value> = Map::new();
    ee.insert("registrikood".to_string(), Value::String(rk));
    ee.insert("name".to_string(), Value::String(name.to_string()));
    if let Some(lf) = infer_ee_legal_form(name) {
        ee.insert("legal_form".to_string(), Value::String(lf));
    }
    if let Some(v) = row.get("status").cloned() {
        ee.insert("status".to_string(), v);
    }
    let location = row
        .get("description")
        .or_else(|| row.get("address"))
        .and_then(|v| v.as_str())
        .map(|s| s.to_string());
    if let Some(s) = location {
        ee.insert("location".to_string(), Value::String(s));
    }
    if let Some(v) = row.get("vat_id").cloned() {
        ee.insert("vat_id".to_string(), v);
    }
    if !segment.is_empty() {
        ee.insert(
            "register_reference".to_string(),
            Value::String(segment.clone()),
        );
    }
    if let Some(v) = row.get("website").cloned() {
        ee.insert("source_url".to_string(), v);
    }
    Some(filter_ee_payload(&ee))
}

pub fn entity_type_from_dutch_rights_form(rights_form: Option<&str>) -> Option<&'static str> {
    let rf = rights_form?;
    let mapping: HashMap<&str, &str> = [
        ("Eenmanszaak", "EMZ"),
        ("Vennootschap Onder Firma", "VOF"),
        ("Maatschap", "MS"),
        ("Stichting", "ST"),
        ("Besloten Vennootschap", "BV"),
    ]
    .into_iter()
    .collect();
    mapping.get(rf).copied()
}

pub fn northdata_register_label(segment: &str) -> Option<&'static str> {
    let s = segment.trim();
    if s.is_empty() {
        return None;
    }
    let pairs: &[(&str, &str)] = &[
        ("Companies House ", "Companies House"),
        ("Siren ", "Siren"),
        ("RIK ", "RIK"),
        ("CRO ", "CRO"),
        ("GEMI ", "GEMI"),
        ("ON ", "ON"),
        ("HRB ", "HRB"),
        ("UID ", "UID"),
        ("MCIT ", "MCIT"),
        ("Handelsregister ", "Handelsregister"),
    ];
    for (prefix, label) in pairs {
        if s.starts_with(prefix) {
            return Some(label);
        }
    }
    if s.to_uppercase().starts_with("KRS") {
        return Some("KRS");
    }
    None
}

pub fn northdata_company_number_from_url(url: &str) -> Option<String> {
    let path = Url::parse(url).ok()?.path().trim_matches('/').to_string();
    if path.is_empty() {
        return None;
    }
    let last = path.split('/').next_back()?;
    let decoded = percent_decode_str(last)
        .decode_utf8()
        .ok()?
        .trim()
        .to_string();
    if decoded.is_empty() {
        None
    } else {
        Some(decoded)
    }
}

pub fn northdata_status_from_title(title: Option<&str>) -> Option<&'static str> {
    let t = title?;
    if t.contains('\u{271d}') || t.contains("✝") {
        return Some("dissolved");
    }
    None
}

pub fn map_northdata_hit_to_global(
    hit: &Map<String, Value>,
    country_code: &str,
) -> Option<Map<String, Value>> {
    if hit.get("type").and_then(|v| v.as_str()) != Some("company") {
        return None;
    }
    let name = hit
        .get("name")
        .or_else(|| hit.get("title"))
        .and_then(|v| v.as_str())?;
    let url = hit.get("url").and_then(|v| v.as_str())?;
    if name.is_empty() || url.is_empty() {
        return None;
    }
    let segment = northdata_company_number_from_url(url)?;
    let cc = country_code.to_uppercase();
    let company_number = segment.clone();
    let hash_input = format!("{}|{}|{}", name, company_number, cc);
    let hash_val = format!("{:x}", Sha256::digest(hash_input.as_bytes()));

    let description = hit.get("description").cloned().unwrap_or(Value::Null);
    let title = hit.get("title").and_then(|v| v.as_str());
    let status = northdata_status_from_title(title).map(|s| Value::String(s.to_string()));
    let register = northdata_register_label(&segment).map(|s| Value::String(s.to_string()));

    let mut global_row = Map::new();
    global_row.insert("name".to_string(), Value::String(name.to_string()));
    global_row.insert("company_number".to_string(), Value::String(company_number));
    global_row.insert("rights_form".to_string(), Value::Null);
    global_row.insert("country_code".to_string(), Value::String(cc));
    if let Some(ref s) = status {
        global_row.insert("status".to_string(), s.clone());
    } else {
        global_row.insert("status".to_string(), Value::Null);
    }
    global_row.insert("last_update".to_string(), Value::Null);
    global_row.insert("description".to_string(), description.clone());
    global_row.insert("region".to_string(), Value::Null);
    global_row.insert("rights_form_code".to_string(), Value::Null);
    global_row.insert("state".to_string(), Value::Null);
    global_row.insert("name_english".to_string(), Value::Null);
    global_row.insert("language".to_string(), Value::Null);
    global_row.insert("hash".to_string(), Value::String(hash_val));
    global_row.insert("address".to_string(), description);
    global_row.insert("start_date".to_string(), Value::Null);
    global_row.insert("legal_persons".to_string(), Value::Null);
    global_row.insert("contact_data".to_string(), Value::Null);
    global_row.insert("activities".to_string(), Value::Null);
    global_row.insert(
        "activity_code_table".to_string(),
        register.unwrap_or(Value::Null),
    );
    global_row.insert("establishment_date".to_string(), Value::Null);
    global_row.insert("establishment_type".to_string(), Value::Null);
    global_row.insert("entity_type".to_string(), Value::Null);
    global_row.insert("vat_id".to_string(), Value::Null);
    global_row.insert("email".to_string(), Value::Null);
    global_row.insert("iban".to_string(), Value::Null);
    global_row.insert("phone".to_string(), Value::Null);
    global_row.insert("postal_code".to_string(), Value::Null);
    global_row.insert("house_number".to_string(), Value::Null);
    global_row.insert("province".to_string(), Value::Null);
    global_row.insert("city".to_string(), Value::Null);
    global_row.insert("street".to_string(), Value::Null);
    global_row.insert("country".to_string(), Value::Null);
    global_row.insert("website".to_string(), Value::String(url.to_string()));

    Some(filter_global_payload(&global_row))
}

pub fn map_kvk_company_to_global(
    company: &Map<String, Value>,
    country_code: &str,
) -> Option<Map<String, Value>> {
    let kvk = company.get("kvk_number").and_then(|v| {
        v.as_i64()
            .or_else(|| v.as_u64().map(|u| u as i64))
            .or_else(|| v.as_str().and_then(|s| s.parse().ok()))
    });
    let name = company.get("name").and_then(|v| v.as_str())?;
    let kvk = kvk?;
    if name.is_empty() {
        return None;
    }

    let cc = country_code.to_uppercase();
    let street = company.get("street").and_then(|v| v.as_str());
    let house_number = company.get("house_number");
    let postal_code = company.get("postal_code").and_then(|v| v.as_str());
    let city = company.get("city").and_then(|v| v.as_str());
    let province = company.get("province").and_then(|v| v.as_str());
    let country = company
        .get("country")
        .and_then(|v| v.as_str())
        .unwrap_or("Nederland");

    let mut address_parts: Vec<String> = vec![];
    if let Some(s) = street {
        address_parts.push(s.to_string());
    }
    if let Some(hn) = house_number {
        if !hn.is_null() {
            address_parts.push(hn.to_string().trim_matches('"').to_string());
        }
    }
    if let Some(s) = postal_code {
        address_parts.push(s.to_string());
    }
    if let Some(s) = city {
        address_parts.push(s.to_string());
    }
    if let Some(s) = province {
        address_parts.push(s.to_string());
    }
    address_parts.push(country.to_string());
    let address = if address_parts.is_empty() {
        None
    } else {
        Some(address_parts.join(", "))
    };

    let company_number = kvk.to_string();
    let hash_input = format!("{}|{}|{}", name, company_number, cc);
    let hash_val = format!("{:x}", Sha256::digest(hash_input.as_bytes()));

    let active = company.get("active").and_then(|v| v.as_bool());
    let status = match active {
        Some(true) => Some("active"),
        Some(false) => Some("inactive"),
        None => None,
    };

    let activities = company.get("activities").cloned();
    let activity_code_table = company
        .get("main_sbi_code")
        .map(|_| Value::String("SBI".to_string()));
    let establishment_type = match company.get("establishment").and_then(|v| v.as_bool()) {
        Some(true) => Some("main"),
        Some(false) => Some("branch"),
        None => None,
    };

    let hn_out = house_number.and_then(|v| {
        if v.is_null() {
            None
        } else {
            Some(Value::String(v.to_string().trim_matches('"').to_string()))
        }
    });

    let mut global_row = Map::new();
    global_row.insert("name".to_string(), Value::String(name.to_string()));
    global_row.insert("company_number".to_string(), Value::String(company_number));
    global_row.insert(
        "rights_form".to_string(),
        company.get("rights_form").cloned().unwrap_or(Value::Null),
    );
    global_row.insert("country_code".to_string(), Value::String(cc));
    global_row.insert(
        "status".to_string(),
        status
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_row.insert("last_update".to_string(), Value::Null);
    global_row.insert(
        "description".to_string(),
        company
            .get("main_activity_description")
            .or_else(|| company.get("main_activity"))
            .cloned()
            .unwrap_or(Value::Null),
    );
    global_row.insert(
        "region".to_string(),
        province
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_row.insert(
        "rights_form_code".to_string(),
        company
            .get("rights_form_code")
            .cloned()
            .unwrap_or(Value::Null),
    );
    global_row.insert(
        "state".to_string(),
        province
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_row.insert("name_english".to_string(), Value::Null);
    global_row.insert("language".to_string(), Value::String("nl".to_string()));
    global_row.insert("hash".to_string(), Value::String(hash_val));
    global_row.insert(
        "address".to_string(),
        address.map(|s| Value::String(s)).unwrap_or(Value::Null),
    );
    global_row.insert("start_date".to_string(), Value::Null);
    global_row.insert("legal_persons".to_string(), Value::Null);
    global_row.insert("contact_data".to_string(), Value::Null);
    global_row.insert("activities".to_string(), activities.unwrap_or(Value::Null));
    global_row.insert(
        "activity_code_table".to_string(),
        activity_code_table.unwrap_or(Value::Null),
    );
    global_row.insert("establishment_date".to_string(), Value::Null);
    global_row.insert(
        "establishment_type".to_string(),
        establishment_type
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_row.insert("entity_type".to_string(), Value::Null);
    global_row.insert(
        "vat_id".to_string(),
        company.get("vat_id").cloned().unwrap_or(Value::Null),
    );
    global_row.insert("email".to_string(), Value::Null);
    global_row.insert("iban".to_string(), Value::Null);
    global_row.insert("phone".to_string(), Value::Null);
    global_row.insert(
        "postal_code".to_string(),
        postal_code
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_row.insert("house_number".to_string(), hn_out.unwrap_or(Value::Null));
    global_row.insert(
        "province".to_string(),
        province
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_row.insert(
        "city".to_string(),
        city.map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_row.insert(
        "street".to_string(),
        street
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_row.insert("country".to_string(), Value::String(country.to_string()));
    global_row.insert("website".to_string(), Value::Null);

    Some(filter_global_payload(&global_row))
}

/// NL script `map_to_global_company` (includes `time` unix field).
pub fn map_nl_company_to_global_script(company: &Map<String, Value>) -> Map<String, Value> {
    let street = company.get("street").and_then(|v| v.as_str());
    let house_number = company.get("house_number");
    let postal_code = company.get("postal_code").and_then(|v| v.as_str());
    let city = company.get("city").and_then(|v| v.as_str());
    let province = company.get("province").and_then(|v| v.as_str());
    let country = company
        .get("country")
        .and_then(|v| v.as_str())
        .unwrap_or("NL");

    let mut address_parts: Vec<String> = vec![];
    if let Some(s) = street {
        address_parts.push(s.to_string());
    }
    if let Some(hn) = house_number {
        if !hn.is_null() {
            address_parts.push(hn.to_string());
        }
    }
    if let Some(s) = postal_code {
        address_parts.push(s.to_string());
    }
    if let Some(s) = city {
        address_parts.push(s.to_string());
    }
    if let Some(s) = province {
        address_parts.push(s.to_string());
    }
    address_parts.push(country.to_string());
    let address = if address_parts.is_empty() {
        None
    } else {
        Some(address_parts.join(", "))
    };

    let name_s = company.get("name").and_then(|v| v.as_str()).unwrap_or("");
    let kvk = company.get("kvk_number");
    let country_for_hash = company
        .get("country")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    let hash_input = format!(
        "{}|{}|{}",
        name_s,
        kvk.map(|v| v.to_string()).unwrap_or_default(),
        country_for_hash
    );
    let hash_val = format!("{:x}", Sha256::digest(hash_input.as_bytes()));

    let active = company.get("active").and_then(|v| v.as_bool());
    let status = match active {
        Some(true) => Some("active"),
        Some(false) => Some("inactive"),
        None => None,
    };

    let activity_code_table = company
        .get("main_sbi_code")
        .map(|_| Value::String("SBI".to_string()));
    let establishment_type = match company.get("establishment").and_then(|v| v.as_bool()) {
        Some(true) => Some("main"),
        Some(false) => Some("branch"),
        None => None,
    };

    let company_number = kvk
        .and_then(|v| {
            if v.is_null() {
                None
            } else {
                Some(v.to_string())
            }
        })
        .filter(|s| !s.is_empty());

    let description = company
        .get("main_activity_description")
        .or_else(|| company.get("main_activity"))
        .cloned();

    let unix_ts = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_secs() as i64)
        .unwrap_or(0);

    let mut global_company = Map::new();
    global_company.insert(
        "name".to_string(),
        company.get("name").cloned().unwrap_or(Value::Null),
    );
    global_company.insert(
        "company_number".to_string(),
        company_number.map(Value::String).unwrap_or(Value::Null),
    );
    global_company.insert(
        "rights_form".to_string(),
        company.get("rights_form").cloned().unwrap_or(Value::Null),
    );
    global_company.insert("country_code".to_string(), Value::String("NL".to_string()));
    global_company.insert(
        "status".to_string(),
        status
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_company.insert("last_update".to_string(), Value::Null);
    global_company.insert(
        "description".to_string(),
        description.unwrap_or(Value::Null),
    );
    global_company.insert(
        "region".to_string(),
        province
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_company.insert(
        "rights_form_code".to_string(),
        company
            .get("rights_form_code")
            .cloned()
            .unwrap_or(Value::Null),
    );
    global_company.insert(
        "state".to_string(),
        province
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_company.insert("name_english".to_string(), Value::Null);
    global_company.insert("language".to_string(), Value::String("nl".to_string()));
    global_company.insert("hash".to_string(), Value::String(hash_val));
    global_company.insert(
        "address".to_string(),
        address.map(|s| Value::String(s)).unwrap_or(Value::Null),
    );
    global_company.insert("start_date".to_string(), Value::Null);
    global_company.insert("legal_persons".to_string(), Value::Null);
    global_company.insert("contact_data".to_string(), Value::Null);
    global_company.insert(
        "activities".to_string(),
        company.get("activities").cloned().unwrap_or(Value::Null),
    );
    global_company.insert(
        "activity_code_table".to_string(),
        activity_code_table.unwrap_or(Value::Null),
    );
    global_company.insert("establishment_date".to_string(), Value::Null);
    global_company.insert(
        "establishment_type".to_string(),
        establishment_type
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_company.insert("entity_type".to_string(), Value::Null);
    global_company.insert(
        "postal_code".to_string(),
        postal_code
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_company.insert(
        "house_number".to_string(),
        house_number.cloned().unwrap_or(Value::Null),
    );
    global_company.insert(
        "street".to_string(),
        street
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_company.insert(
        "province".to_string(),
        province
            .map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_company.insert(
        "city".to_string(),
        city.map(|s| Value::String(s.to_string()))
            .unwrap_or(Value::Null),
    );
    global_company.insert("country".to_string(), Value::String(country.to_string()));
    global_company.insert("time".to_string(), Value::Number(Number::from(unix_ts)));
    global_company.remove("id");
    global_company.remove("created_at");
    global_company
}
