//! Dutch KVK zoeken API — mirrors `fetch_kvk_page` / `fetch_all_kvk_companies` / `parse_nl_company_item`
//! from `scripts/scrape_nl_comps.py`.

use serde_json::{Map, Number, Value};

const KVK_PROFILE_ID: &str = "5C10A89D-635E-49CC-94B8-042DD533B64A";
const PAGE_SIZE: i64 = 50;
const MAX_START: i64 = 5000;

pub async fn fetch_kvk_page(
    client: &reqwest::Client,
    base_url: &str,
    query: &str,
    start: i64,
    size: i64,
) -> Option<Value> {
    if start >= MAX_START {
        tracing::warn!("KVK start {} >= {}, skipping", start, MAX_START);
        return None;
    }
    let url = format!(
        "{}?q={}&language=nl&site=kvk2014&size={}&start={}&inschrijvingsstatus=ingeschreven",
        base_url.trim_end_matches('/'),
        encode_query_component(query),
        size,
        start
    );
    let resp = client
        .get(&url)
        .header("profileId", KVK_PROFILE_ID)
        .send()
        .await
        .ok()?;
    if !resp.status().is_success() {
        let status = resp.status();
        let text = resp.text().await.unwrap_or_default();
        tracing::warn!(
            "KVK fetch failed {}: {}",
            status,
            text.chars().take(200).collect::<String>()
        );
        return None;
    }
    resp.json().await.ok()
}

/// `percent-encoding` style for query param (space → + is not used; use %20).
fn encode_query_component(s: &str) -> String {
    use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
    utf8_percent_encode(s, NON_ALPHANUMERIC).to_string()
}

fn parse_nl_company_item(item: &Value) -> Option<Map<String, Value>> {
    let item = item.as_object()?;
    let bezoek = item.get("bezoeklocatie").and_then(|v| v.as_object());

    let street = bezoek
        .and_then(|b| b.get("straat"))
        .and_then(|v| v.as_str())
        .map(String::from);
    let house_number_raw = bezoek.and_then(|b| b.get("huisnummer"));
    let house_number = house_number_raw.and_then(|v| {
        if let Some(s) = v.as_str() {
            return s
                .parse::<i64>()
                .ok()
                .map(|n| Value::Number(Number::from(n)));
        }
        v.as_i64()
            .or_else(|| v.as_u64().map(|u| u as i64))
            .map(|n| Value::Number(Number::from(n)))
    });
    let postal_code = bezoek
        .and_then(|b| b.get("postcode"))
        .and_then(|v| v.as_str())
        .map(String::from);
    let city = bezoek
        .and_then(|b| b.get("plaats"))
        .and_then(|v| v.as_str())
        .map(String::from);
    let area = bezoek
        .and_then(|b| b.get("wijk"))
        .and_then(|v| v.as_str())
        .map(String::from);
    let neighborhood = bezoek
        .and_then(|b| b.get("buurt"))
        .and_then(|v| v.as_str())
        .map(String::from);
    let province = bezoek
        .and_then(|b| b.get("provincie"))
        .and_then(|v| v.as_str())
        .map(String::from);
    let municipality = bezoek
        .and_then(|b| b.get("gemeente"))
        .and_then(|v| v.as_str())
        .map(String::from);
    let country = bezoek
        .and_then(|b| b.get("land"))
        .and_then(|v| v.as_str())
        .unwrap_or("Nederland");

    let longitude = bezoek
        .and_then(|b| b.get("longitude"))
        .and_then(|v| v.as_f64());
    let latitude = bezoek
        .and_then(|b| b.get("latitude"))
        .and_then(|v| v.as_f64());

    let kvk_number: Option<i64> = item.get("kvkNummer").and_then(|v| {
        if let Some(s) = v.as_str() {
            return s.parse().ok();
        }
        v.as_i64().or_else(|| v.as_u64().map(|u| u as i64))
    });
    if kvk_number.is_none() {
        return None;
    }

    let establishment_number = item.get("vestigingsnummer").and_then(|v| {
        if let Some(s) = v.as_str() {
            return s.parse().ok();
        }
        v.as_i64().or_else(|| v.as_u64().map(|u| u as i64))
    });

    let vat_id = item
        .get("btwNummer")
        .and_then(|v| v.as_str())
        .map(String::from);
    let rsin = item.get("rsin").and_then(|v| v.as_str()).map(String::from);

    let active = item.get("actief").and_then(|v| v.as_bool());

    let rights_form = item
        .get("rechtsvormOmschrijving")
        .and_then(|v| v.as_str())
        .map(String::from);
    let rights_form_code = item
        .get("rechtsvorm")
        .and_then(|v| v.as_str())
        .map(String::from);

    let activities = item.get("activiteiten").and_then(|v| v.as_array());
    let mut main_activity: Option<String> = None;
    let mut main_activity_description: Option<String> = None;
    let mut main_sbi_code: Option<i64> = None;
    let mut standard_business_layout: Option<Value> = None;
    let activities_json: Option<Value> = activities.map(|a| Value::Array(a.clone()));

    if let Some(acts) = activities {
        let main = acts
            .iter()
            .find(|a| a.get("hoofdactiviteit") == Some(&Value::Bool(true)))
            .or_else(|| acts.first());
        if let Some(m) = main {
            main_activity = m
                .get("omschrijving")
                .and_then(|v| v.as_str())
                .map(String::from);
            main_activity_description = main_activity.clone();
            main_sbi_code = m.get("code").and_then(|v| {
                if let Some(s) = v.as_str() {
                    return s.parse().ok();
                }
                v.as_i64().or_else(|| v.as_u64().map(|u| u as i64))
            });
            standard_business_layout = m.get("sbiCode").cloned();
        }
    }

    let establishment = item
        .get("inschrijvingstype")
        .and_then(|v| v.as_str())
        .map(|s| s.to_lowercase() == "hoofdvestiging");

    let name = item.get("naam").and_then(|v| v.as_str())?;
    if name.is_empty() {
        return None;
    }

    let activities_serialized = activities_json
        .as_ref()
        .and_then(|a| serde_json::to_string(a).ok())
        .map(Value::String);

    let mut company = Map::new();
    company.insert(
        "kvk_number".to_string(),
        Value::Number(Number::from(kvk_number.unwrap())),
    );
    company.insert("name".to_string(), Value::String(name.to_string()));
    if let Some(s) = postal_code {
        company.insert("postal_code".to_string(), Value::String(s));
    }
    if let Some(a) = active {
        company.insert("active".to_string(), Value::Bool(a));
    }
    if let Some(s) = street {
        company.insert("street".to_string(), Value::String(s));
    }
    if let Some(s) = area {
        company.insert("area".to_string(), Value::String(s));
    }
    if let Some(x) = longitude {
        company.insert("longitude".to_string(), serde_json::json!(x));
    }
    if let Some(x) = latitude {
        company.insert("latitude".to_string(), serde_json::json!(x));
    }
    if let Some(s) = vat_id {
        company.insert("vat_id".to_string(), Value::String(s));
    }
    if let Some(s) = rsin {
        company.insert("rsin".to_string(), Value::String(s));
    }
    if let Some(n) = establishment_number {
        company.insert(
            "establishment_number".to_string(),
            Value::Number(Number::from(n)),
        );
    }
    if let Some(s) = rights_form {
        company.insert("rights_form".to_string(), Value::String(s));
    }
    if let Some(s) = province {
        company.insert("province".to_string(), Value::String(s));
    }
    if let Some(s) = municipality {
        company.insert("municipality".to_string(), Value::String(s));
    }
    if let Some(v) = standard_business_layout {
        company.insert("standard_business_layout".to_string(), v);
    }
    if let Some(s) = rights_form_code {
        company.insert("rights_form_code".to_string(), Value::String(s));
    }
    if let Some(est) = establishment {
        company.insert("establishment".to_string(), Value::Bool(est));
    }
    if let Some(hn) = house_number {
        company.insert("house_number".to_string(), hn);
    }
    if let Some(s) = neighborhood {
        company.insert("neighborhood".to_string(), Value::String(s));
    }
    company.insert("country".to_string(), Value::String(country.to_string()));
    if let Some(a) = activities_serialized {
        company.insert("activities".to_string(), a);
    }
    if let Some(s) = city {
        company.insert("city".to_string(), Value::String(s));
    }
    if let Some(s) = main_activity {
        company.insert("main_activity".to_string(), Value::String(s));
    }
    if let Some(s) = main_activity_description {
        company.insert("main_activity_description".to_string(), Value::String(s));
    }
    if let Some(n) = main_sbi_code {
        company.insert("main_sbi_code".to_string(), Value::Number(Number::from(n)));
    }

    Some(company)
}

fn items_from_page(data: &Value) -> Vec<Value> {
    data.get("data")
        .and_then(|d| d.get("items"))
        .and_then(|i| i.as_array())
        .map(|a| a.clone())
        .unwrap_or_default()
}

pub async fn fetch_all_kvk_companies(
    client: &reqwest::Client,
    base_url: &str,
    query: &str,
) -> Vec<Map<String, Value>> {
    let mut all: Vec<Map<String, Value>> = vec![];
    let first = match fetch_kvk_page(client, base_url, query, 0, PAGE_SIZE).await {
        Some(v) => v,
        None => return all,
    };
    let total_hits = first
        .get("data")
        .and_then(|d| d.get("numberOfHits"))
        .and_then(|n| n.as_i64())
        .or_else(|| {
            first
                .get("data")
                .and_then(|d| d.get("numberOfHits"))
                .and_then(|n| n.as_u64())
                .map(|u| u as i64)
        })
        .unwrap_or(0);

    for item in items_from_page(&first) {
        if let Some(m) = parse_nl_company_item(&item) {
            all.push(m);
        }
    }

    let mut starts: Vec<i64> = vec![];
    let mut start = PAGE_SIZE;
    while start < total_hits && start < MAX_START {
        starts.push(start);
        start += PAGE_SIZE;
    }

    tracing::info!(
        "KVK query '{}': {} hits, fetching {} page(s)",
        query,
        total_hits,
        1 + starts.len()
    );

    let mut tasks = Vec::new();
    for s in starts {
        let c: reqwest::Client = client.clone();
        let u: String = base_url.to_string();
        let q = query.to_string();
        tasks.push(tokio::spawn(async move {
            fetch_kvk_page(&c, &u, &q, s, PAGE_SIZE).await
        }));
    }

    for t in tasks {
        if let Ok(Some(page)) = t.await {
            for item in items_from_page(&page) {
                if let Some(m) = parse_nl_company_item(&item) {
                    all.push(m);
                }
            }
        }
    }

    all
}
