//! Field coverage tests that verify our model structs aren't silently
//! dropping unknown JSON fields from the SEC EDGAR API.
//!
//! Uses a roundtrip approach: deserialize raw JSON into a typed struct,
//! serialize back to `serde_json::Value`, and compare keys. Any keys
//! present in the raw JSON but missing from the roundtrip are fields
//! our structs don't model.
//!
//! These are `#[ignore]` by default (they hit the real API). Run with:
//!
//!     cargo test --test field_coverage -- --ignored

use std::collections::HashMap;

use serde::Serialize;
use serde::de::DeserializeOwned;
use serde_json::Value;

use edgar_rs::*;

const USER_AGENT: &str = "edgar-rs-field-coverage/0.1.0 test@example.com";
const APPLE_CIK: u64 = 320193;

// ─── Recursive key comparator ────────────────────────────

fn find_missing_keys<T: DeserializeOwned + Serialize>(raw_json: &str) -> Vec<String> {
    let raw: Value = serde_json::from_str(raw_json).expect("failed to parse raw JSON");
    let typed: T = serde_json::from_str(raw_json).expect("failed to deserialize into typed struct");
    let roundtrip: Value = serde_json::to_value(&typed).expect("failed to serialize back");
    collect_missing_keys(&raw, &roundtrip, "$")
}

fn collect_missing_keys(raw: &Value, roundtrip: &Value, path: &str) -> Vec<String> {
    match (raw, roundtrip) {
        (Value::Object(raw_map), Value::Object(rt_map)) => {
            let mut missing = Vec::new();
            for key in raw_map.keys() {
                let child_path = format!("{path}.{key}");
                if !rt_map.contains_key(key) {
                    missing.push(child_path);
                } else {
                    missing.extend(collect_missing_keys(
                        &raw_map[key],
                        &rt_map[key],
                        &child_path,
                    ));
                }
            }
            missing
        }
        (Value::Array(raw_arr), Value::Array(rt_arr)) => match (raw_arr.first(), rt_arr.first()) {
            (Some(raw_first), Some(rt_first)) => {
                collect_missing_keys(raw_first, rt_first, &format!("{path}[0]"))
            }
            _ => vec![],
        },
        _ => vec![],
    }
}

// ─── HTTP helper ─────────────────────────────────────────

async fn fetch_raw(url: &str) -> String {
    let client = reqwest::Client::new();
    let resp = client
        .get(url)
        .header(reqwest::header::USER_AGENT, USER_AGENT)
        .send()
        .await
        .unwrap_or_else(|e| panic!("GET {url} failed: {e}"));
    assert!(
        resp.status().is_success(),
        "GET {url} returned {}",
        resp.status()
    );
    resp.text().await.unwrap()
}

// ─── Tests ───────────────────────────────────────────────

#[tokio::test]
#[ignore]
async fn field_coverage_tickers() {
    let raw = fetch_raw("https://www.sec.gov/files/company_tickers.json").await;
    // Raw JSON is {"0": {ticker}, "1": {ticker}, ...} — check inner Ticker objects
    let map: HashMap<String, Value> = serde_json::from_str(&raw).unwrap();
    let first_value = map.values().next().expect("no tickers in response");
    let raw_ticker = serde_json::to_string(first_value).unwrap();
    let missing = find_missing_keys::<Ticker>(&raw_ticker);
    assert!(
        missing.is_empty(),
        "Ticker struct is missing fields: {missing:?}"
    );
}

#[tokio::test]
#[ignore]
async fn field_coverage_submission() {
    let cik = format!("{APPLE_CIK:010}");
    let raw = fetch_raw(&format!("https://data.sec.gov/submissions/CIK{cik}.json")).await;
    let missing = find_missing_keys::<Submission>(&raw);
    assert!(
        missing.is_empty(),
        "Submission struct is missing fields: {missing:?}"
    );
}

#[tokio::test]
#[ignore]
async fn field_coverage_company_concept() {
    let cik = format!("{APPLE_CIK:010}");
    let raw = fetch_raw(&format!(
        "https://data.sec.gov/api/xbrl/companyconcept/CIK{cik}/us-gaap/Revenues.json"
    ))
    .await;
    let missing = find_missing_keys::<CompanyConcept>(&raw);
    assert!(
        missing.is_empty(),
        "CompanyConcept struct is missing fields: {missing:?}"
    );
}

#[tokio::test]
#[ignore]
async fn field_coverage_company_facts() {
    let cik = format!("{APPLE_CIK:010}");
    let raw = fetch_raw(&format!(
        "https://data.sec.gov/api/xbrl/companyfacts/CIK{cik}.json"
    ))
    .await;
    let missing = find_missing_keys::<CompanyFacts>(&raw);
    assert!(
        missing.is_empty(),
        "CompanyFacts struct is missing fields: {missing:?}"
    );
}

#[tokio::test]
#[ignore]
async fn field_coverage_frame() {
    let raw = fetch_raw(
        "https://data.sec.gov/api/xbrl/frames/us-gaap/AccountsPayableCurrent/USD/CY2023Q4I.json",
    )
    .await;
    let missing = find_missing_keys::<Frame>(&raw);
    assert!(
        missing.is_empty(),
        "Frame struct is missing fields: {missing:?}"
    );
}

#[tokio::test]
#[ignore]
async fn field_coverage_search() {
    let raw = fetch_raw("https://efts.sec.gov/LATEST/search-index?q=Apple&forms=10-K").await;
    let missing = find_missing_keys::<EftsResponse>(&raw);
    assert!(
        missing.is_empty(),
        "EftsResponse struct is missing fields: {missing:?}"
    );
}
