"""What the generator promises about every world, checked over every digested seed."""

from __future__ import annotations

import json
from collections import Counter

import pytest

from alert_triage.generator import (
    BENIGN_PCT,
    RULE_IDS,
    VISIBLE_PCT,
    canonical_json,
    generate,
    is_held_out,
    screen_of,
)
from alert_triage.rng import days_from_civil

ALL = range(4096)


@pytest.fixture(scope="module")
def worlds():
    return [generate(s) for s in ALL]


def _day(iso: str) -> int:
    y, m, d = (int(x) for x in iso.split("-"))
    return days_from_civil(y, m, d)


def test_deterministic() -> None:
    assert canonical_json(generate(17)) == canonical_json(generate(17))
    assert canonical_json(generate(17)) != canonical_json(generate(18))


def test_canonical_json_has_no_floats(worlds) -> None:
    def walk(v):
        if isinstance(v, float):
            raise AssertionError("float in world")
        if isinstance(v, dict):
            for x in v.values():
                walk(x)
        if isinstance(v, list):
            for x in v:
                walk(x)
    for w in worlds[:512]:
        walk(w)
        assert json.loads(canonical_json(w)) == w


def test_mix_by_seed_is_close_to_declared(worlds) -> None:
    tiers = Counter(w["tier"] for w in worlds)
    n = len(worlds)
    assert abs(tiers["benign"] / n - BENIGN_PCT / 100) < 0.03
    assert abs(tiers["visible"] / n - VISIBLE_PCT / 100) < 0.03
    assert abs(tiers["hidden"] / n - (100 - BENIGN_PCT - VISIBLE_PCT) / 100) < 0.03


def test_every_rule_carries_both_labels(worlds) -> None:
    """CHEAT 3: no rule id may be a label. Each fires on benign AND suspicious seeds."""
    seen = Counter((w["alert"]["rule"], w["label"]) for w in worlds)
    for table in RULE_IDS.values():
        for rid in table.values():
            assert seen[(rid, "benign")] > 0, rid
            assert seen[(rid, "suspicious")] > 0, rid


def test_every_hidden_signal_lands_on_several_templates(worlds) -> None:
    """CHEAT 4: no fixed template -> lookup mapping to memorise."""
    pairs = Counter((w["overlay"], w["template"]) for w in worlds if w["overlay"])
    by_kind: dict[str, set[str]] = {}
    for (kind, template), _ in pairs.items():
        by_kind.setdefault(kind, set()).add(template)
    assert len(by_kind["funnel"]) == 6
    assert len(by_kind["serial_closer"]) >= 4
    assert len(by_kind["doc_mismatch"]) == 3


def test_overlay_only_on_benign_screens(worlds) -> None:
    for w in worlds:
        if w["overlay"]:
            assert w["template"].startswith("B") and w["tier"] == "hidden" and w["label"] == "suspicious"
        if w["template"].startswith("S"):
            assert w["label"] == "suspicious" and w["overlay"] is None


def test_nothing_is_dated_on_or_after_the_fire_date(worlds) -> None:
    for w in worlds:
        fired = _day(w["alert"]["fired"])
        assert all(_day(t["date"]) < fired for t in w["transactions"]), w["seed"]


def test_transaction_ids_are_chronological_and_dense(worlds) -> None:
    for w in worlds[:512]:
        ids = [t["id"] for t in w["transactions"]]
        assert ids == [f"T-{i + 1}" for i in range(len(ids))]
        days = [_day(t["date"]) for t in w["transactions"]]
        assert days == sorted(days)


def test_triggering_set_is_non_empty_and_shown(worlds) -> None:
    for w in worlds:
        ids = {t["id"] for t in w["transactions"]}
        assert w["alert"]["triggering"], w["seed"]
        assert set(w["alert"]["triggering"]) <= ids


def test_planted_is_citable_after_the_right_lookups(worlds) -> None:
    """Every planted id exists in the world: a kyc field, a T-id, a CP-id, a doc or a prior alert."""
    for w in worlds:
        universe = {f"kyc.{k}" for k in w["customer"]} | {t["id"] for t in w["transactions"]}
        universe |= {c["id"] for c in w["counterparties"]} | {d["id"] for d in w["documents"]} | {p["id"] for p in w["prior_alerts"]}
        assert w["planted"], w["seed"]
        for alt in w["planted"]:
            assert alt and set(alt) <= universe, (w["seed"], alt)


def test_benign_screens_always_carry_an_outbound_counterparty(worlds) -> None:
    for w in worlds:
        if w["template"].startswith("B"):
            assert any(c["direction"] == "out" for c in w["screen_counterparties"]), w["seed"]
            assert len(w["screen_counterparties"]) == 2
        assert 1 <= len(w["screen_counterparties"]) <= 2


def test_prior_alert_count_on_screen_matches_the_lookup(worlds) -> None:
    for w in worlds:
        assert len(w["prior_alerts"]) == w["customer"]["prior_alerts"]


def test_held_out_slice_moves_the_threshold_and_the_screen(worlds) -> None:
    held = [w for w in worlds if w["held_out"]]
    main = [w for w in worlds if not w["held_out"]]
    assert 0.10 < len(held) / len(worlds) < 0.15
    for w in held:
        assert is_held_out(w["seed"])
        assert w["currency"] == "KRN" and w["threshold"] == 2_000_000 and w["screen_order"] == "kyc_first"
        assert w["alert"]["rule"] in RULE_IDS["held_out"].values()
        assert w["customer"]["country"] == "Karnland"
    for w in main:
        assert w["currency"] == "USD" and w["threshold"] == 1_000_000 and w["screen_order"] == "alert_first"
        assert w["alert"]["rule"] in RULE_IDS["main"].values()


def test_structuring_band_is_threshold_relative(worlds) -> None:
    for w in worlds:
        if w["template"] == "S1":
            lo, hi = w["threshold"] * 92 // 100, w["threshold"] - 1
            deposits = [t for t in w["transactions"] if t["id"] in w["planted"][0]]
            assert len(deposits) == 8
            assert all(lo <= t["amount"] <= hi for t in deposits)
            assert len({t["branch"] for t in deposits}) == 3


def test_screen_carries_no_hidden_field(worlds) -> None:
    hidden = {"label", "typology", "planted", "overlay", "tier", "template", "reference_minutes", "reference_policy",
              "prior_alerts_list", "documents", "counterparties", "transactions", "other_alerted_accounts", "party", "rationale"}
    for w in worlds[:256]:
        text = canonical_json(screen_of(w))
        for key in hidden:
            assert f'"{key}"' not in text, key
