//! Registry-level parity guards. These tests keep only daemon-level invariants
//! and independent expected sets; per-module descriptor shape is covered by each
//! module's own `commands_are_well_formed` test.

use tail_fin_daemon::registry::all_site_entries;

const HTTP_SITE_IDS: &[&str] = &[
    "arkham",
    "alphora",
    "gemini",
    "nansen",
    "macromicro",
    "tradingview",
    "591",
    // sa is dual-mode (Transport::Either) but daemon-served over HTTP
    // (from_http_module -> SiteTransport::Http; browser stays in-process).
    "sa",
];

#[test]
fn every_registered_site_is_descriptor_backed_namespaced_and_unique() {
    assert!(
        !all_site_entries().is_empty(),
        "expected at least one registered descriptor-backed site"
    );

    let mut all_wires = std::collections::BTreeSet::new();
    for e in all_site_entries() {
        let id = e.site.id();
        assert!(!e.specs.is_empty(), "{id}: empty descriptor table");
        for s in e.specs {
            assert!(
                s.wire.starts_with(&format!("{id}.")),
                "{id}: wire '{}' not namespaced",
                s.wire
            );
            assert!(
                all_wires.insert(s.wire),
                "duplicate wire across sites: {}",
                s.wire
            );
        }
    }
}

#[test]
fn http_sites_are_registered_as_http_transport() {
    use tail_fin_daemon::driver::SiteTransport;

    for id in HTTP_SITE_IDS {
        let e = site_entry(id);
        assert!(!e.specs.is_empty(), "{id} must have descriptor specs");
        assert_eq!(
            e.transport(),
            SiteTransport::Http,
            "{id} must be tagged SiteTransport::Http"
        );
    }
}

#[test]
fn http_only_site_entries_have_http_specs() {
    use tail_fin_core::Transport;

    for id in HTTP_SITE_IDS.iter().copied().filter(|id| *id != "sa") {
        let e = site_entry(id);
        assert!(
            e.specs
                .iter()
                .all(|s| matches!(s.transport, Transport::Http)),
            "{id}: HTTP daemon entry must expose only HTTP command specs"
        );
    }
}

#[test]
fn sa_registry_shape_matches_feature_coherent_command_set() {
    use tail_fin_core::Transport;

    let sa = site_entry("sa");
    let sa_wires: Vec<&str> = sa.specs.iter().map(|s| s.wire).collect();
    let sa_has_export = sa_wires.contains(&"sa.export-cookies");

    // Isolated daemon builds compile sa HTTP-only (38 wires). Full workspace
    // tests may unify tail-fin-sa/browser and include export-cookies (39 wires).
    assert!(
        (sa_wires.len() == 38 && !sa_has_export) || (sa_wires.len() == 39 && sa_has_export),
        "sa command set must be HTTP-only (38 without export-cookies) or browser-unified (39 with export-cookies); got len={}, has_export={}",
        sa_wires.len(),
        sa_has_export
    );

    for s in sa.specs {
        if s.wire == "sa.export-cookies" {
            assert!(
                matches!(s.transport, Transport::Browser),
                "sa.export-cookies must remain browser-only"
            );
        } else {
            assert!(
                matches!(s.transport, Transport::Either),
                "sa: non-export wire '{}' must remain dual-mode",
                s.wire
            );
        }
    }
}

#[test]
fn s591_daemon_entry_matches_expected_wire_contract() {
    // The full 15-wire descriptor set (crawl is CLI-only, not a wire).
    assert_registered_wire_set(
        "591",
        &[
            "591.hot",
            "591.community",
            "591.price-history",
            "591.sales",
            "591.nearby",
            "591.sale-list",
            "591.newhouse-list",
            "591.newhouse-detail",
            "591.rent-detail",
            "591.sale-detail",
            "591.coordinate-area",
            "591.high-value",
            "591.community-rank",
            "591.regions",
            "591.search",
        ],
    );
}

#[test]
fn tradingview_daemon_entry_matches_expected_wire_contract() {
    // The full 9-wire descriptor set (stream is CLI-only, not a wire).
    assert_registered_wire_set(
        "tradingview",
        &[
            "tradingview.history",
            "tradingview.quote",
            "tradingview.search",
            "tradingview.news",
            "tradingview.economic-calendar",
            "tradingview.screener",
            "tradingview.batch",
            "tradingview.pine-public",
            "tradingview.pine-scrape",
        ],
    );
}

fn site_entry(id: &str) -> &'static tail_fin_daemon::SiteEntry {
    all_site_entries()
        .iter()
        .find(|e| e.site.id() == id)
        .unwrap_or_else(|| panic!("{id} must be registered in the daemon"))
}

fn assert_registered_wire_set(id: &str, expected: &[&'static str]) {
    let actual_set = registered_wire_set(id);
    let expected_set: std::collections::BTreeSet<&str> = expected.iter().copied().collect();
    assert_eq!(
        actual_set, expected_set,
        "{id} daemon wire set must match the expected contract"
    );
}

fn registered_wire_set(id: &str) -> std::collections::BTreeSet<&'static str> {
    site_entry(id).specs.iter().map(|s| s.wire).collect()
}
