//! ФНС ЕГРЮЛ/ЕГРИП search at egrul.nalog.ru — mirrored from the browser flow:
//! GET `/` (session cookie) → POST `/` (x-www-form-urlencoded) → `t` token →
//! GET `/search-result/{t}?r=&_=`.
//!
//! Endpoints and form fields follow the public web UI; FNS may rate-limit or change behavior.

use std::time::Duration;

use crate::audit::{logged_send, AuditBus, HttpAuditMeta};
use serde_json::{json, Map, Value};
use tokio::time::sleep;

pub const DEFAULT_UA: &str = concat!(
    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ",
    "(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
);

const MAX_RETRIES: u32 = 3;

/// Returns raw `rows` entries from the search-result JSON (single-letter keys: `n`, `o`, `i`, …).
pub async fn fetch_egrul_rows(
    client: &reqwest::Client,
    base_url: &str,
    query: &str,
    audit: Option<&AuditBus>,
    http_audit_profile: Option<String>,
) -> Vec<Map<String, Value>> {
    let root = base_url.trim_end_matches('/');
    if query.trim().is_empty() {
        return vec![];
    }

    let mut delay_secs = 1.0_f64;
    let mut last_err = String::new();

    for attempt in 0..MAX_RETRIES {
        match egrul_search_once(client, root, query, audit, http_audit_profile.clone()).await {
            Ok(rows) => return rows,
            Err(e) => {
                last_err = e.clone();
                tracing::warn!("EGRUL search error (attempt {}): {}", attempt + 1, e);
                sleep(Duration::from_secs_f64(delay_secs)).await;
                delay_secs = (delay_secs * 2.0).min(15.0);
            }
        }
    }

    tracing::warn!("EGRUL: giving up after retries: {}", last_err);
    vec![]
}

async fn egrul_search_once(
    client: &reqwest::Client,
    root: &str,
    query: &str,
    audit: Option<&AuditBus>,
    http_audit_profile: Option<String>,
) -> Result<Vec<Map<String, Value>>, String> {
    let home = format!("{root}/");
    let warm_meta = HttpAuditMeta {
        source: "egrul_warm",
        profile_name: http_audit_profile.clone(),
        config_name: None,
        extra: None,
    };
    let warm = logged_send(
        audit,
        client
            .get(&home)
            .header("user-agent", DEFAULT_UA)
            .header("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
            .header(
                "accept-language",
                "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7",
            )
            .send(),
        "GET",
        &home,
        warm_meta,
    )
    .await;
    if let Err(e) = warm {
        tracing::debug!("EGRUL GET / (warm) failed (continuing): {}", e);
    }

    let form = [
        ("vyp3CaptchaToken", ""),
        ("page", ""),
        ("query", query),
        ("nameEq", ""),
        ("region", ""),
        ("PreventChromeAutocomplete", ""),
    ];

    let post_meta = HttpAuditMeta {
        source: "egrul_post",
        profile_name: http_audit_profile.clone(),
        config_name: None,
        extra: Some(json!({ "query_len": query.len() })),
    };
    let post = logged_send(
        audit,
        client
            .post(&home)
            .header("user-agent", DEFAULT_UA)
            .header("accept", "application/json, text/javascript, */*; q=0.01")
            .header("accept-language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7")
            .header("origin", root)
            .header("referer", &home)
            .form(&form)
            .send(),
        "POST",
        &home,
        post_meta,
    )
    .await
    .map_err(|e| e.to_string())?;

    let status = post.status();
    if !status.is_success() {
        let text = post.text().await.unwrap_or_default();
        return Err(format!(
            "POST {} status {}: {}",
            home,
            status,
            text.chars().take(200).collect::<String>()
        ));
    }

    let val: Value = post.json().await.map_err(|e| e.to_string())?;
    let captcha = val.get("captchaRequired").and_then(|v| v.as_bool()).unwrap_or(false);
    if captcha {
        return Err("EGRUL returned captchaRequired=true; automated search blocked".to_string());
    }
    let token = val
        .get("t")
        .and_then(|v| v.as_str())
        .filter(|s| !s.is_empty())
        .ok_or_else(|| "missing token `t` in EGRUL POST response".to_string())?;

    let ts_ms = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis())
        .unwrap_or(0);
    let result_url = format!("{root}/search-result/{token}?r={ts_ms}&_={ts_ms}");

    let res_meta = HttpAuditMeta {
        source: "egrul_result",
        profile_name: http_audit_profile,
        config_name: None,
        extra: Some(json!({ "query_len": query.len() })),
    };
    let resp = logged_send(
        audit,
        client
            .get(&result_url)
            .header("user-agent", DEFAULT_UA)
            .header("accept", "application/json, text/javascript, */*; q=0.01")
            .header("accept-language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7")
            .header("referer", &home)
            .send(),
        "GET",
        &result_url,
        res_meta,
    )
    .await
    .map_err(|e| e.to_string())?;

    let status = resp.status();
    if !status.is_success() {
        let text = resp.text().await.unwrap_or_default();
        return Err(format!(
            "GET search-result status {}: {}",
            status,
            text.chars().take(200).collect::<String>()
        ));
    }

    let body: Value = resp.json().await.map_err(|e| e.to_string())?;
    let rows = body.get("rows").and_then(|r| r.as_array());
    let Some(arr) = rows else {
        return Ok(vec![]);
    };

    Ok(arr
        .iter()
        .filter_map(|v| v.as_object().cloned())
        .collect())
}
