import json
from types import SimpleNamespace

from gui_v6 import diagnostics


def _doc(**kw):
    base = dict(ordinal=0, status="success", error_type=None, error_code=None, duration_ms=12)
    base.update(kw)
    return SimpleNamespace(**base)


def test_new_run_id_is_hex():
    rid = diagnostics.new_run_id()
    assert isinstance(rid, str) and len(rid) >= 16


def test_items_from_summary_whitelist_only():
    summary = SimpleNamespace(documents=[
        _doc(ordinal=0, status="success"),
        _doc(ordinal=1, status="failed", error_type="ValueError", error_code="processing_error"),
    ])
    items = diagnostics.items_from_summary(summary)
    assert items[1]["error_type"] == "ValueError"
    assert set(items[0]) <= {"ordinal", "status", "error_type", "error_code", "duration_ms"}


def test_build_payload_counts_and_no_pii_leak():
    # On INJECTE de la PII via des clés interdites + un faux message d'erreur :
    raw_docs = [
        {"ordinal": 0, "status": "success", "duration_ms": 5,
         "filename": "LETTRE Dupont 1980.pdf", "path": "/home/dom/secret.pdf"},
        {"ordinal": 1, "status": "failed", "error_type": "ValueError",
         "error_code": "processing_error", "error_message": "patient Dupont Jean"},
    ]
    payload = diagnostics.build_diagnostics_payload(
        run_id="r" * 16, app_name="gui_v6", app_version="6.0.0-g1",
        license_ref="LIC-1", machine_id="m" * 12, duration_ms=999, items=raw_docs,
    )
    assert payload["document_count"] == 2
    assert payload["succeeded_count"] == 1 and payload["failed_count"] == 1
    blob = json.dumps(payload).lower()
    for forbidden in ("filename", "path", "secret", "dupont", "lettre", "error_message", "patient"):
        assert forbidden not in blob, f"fuite RGPD : {forbidden}"
    for item in payload["items"]:
        assert set(item) <= {"ordinal", "status", "error_type", "error_code", "duration_ms"}


class _FakeResp:
    def __init__(self, status_code):
        self.status_code = status_code


class _FakeSession:
    def __init__(self, status_code=200, raise_exc=None):
        self.status_code = status_code
        self.raise_exc = raise_exc
        self.calls = []

    def post(self, url, json=None, timeout=None):
        self.calls.append((url, json, timeout))
        if self.raise_exc:
            raise self.raise_exc
        return _FakeResp(self.status_code)


def test_client_report_ok_on_2xx():
    sess = _FakeSession(status_code=200)
    client = diagnostics.DiagnosticsClient("https://app.aivanov.eu/", session=sess)
    assert client.report({"run_id": "r"}) is True
    assert sess.calls[0][0] == "https://app.aivanov.eu/api/v1/diagnostics/report"


def test_client_report_false_on_network_error_without_raising():
    sess = _FakeSession(raise_exc=RuntimeError("no network"))
    client = diagnostics.DiagnosticsClient("https://app.aivanov.eu", session=sess)
    assert client.report({"run_id": "r"}) is False  # ne lève pas


def test_report_run_diagnostics_no_send_without_license(tmp_path):
    sess = _FakeSession()
    ok = diagnostics.report_run_diagnostics(
        SimpleNamespace(documents=[]), base_url="https://app.aivanov.eu",
        license_ref=None, machine_id="m" * 12, session=sess,
        spool_path=tmp_path / "spool.jsonl",
    )
    assert ok is False and sess.calls == []


def test_report_run_diagnostics_network_down_spools(tmp_path):
    sess = _FakeSession(raise_exc=RuntimeError("down"))
    spool = tmp_path / "spool.jsonl"
    summary = SimpleNamespace(documents=[_doc(ordinal=0, status="failed",
                                              error_type="ValueError", error_code="processing_error")])
    ok = diagnostics.report_run_diagnostics(
        summary, base_url="https://app.aivanov.eu", license_ref="LIC-1",
        machine_id="m" * 12, session=sess, spool_path=spool,
    )
    assert ok is False and spool.exists()
    line = json.loads(spool.read_text(encoding="utf-8").splitlines()[0])
    assert line["failed_count"] == 1


def test_flush_spool_sends_and_clears(tmp_path):
    spool = tmp_path / "spool.jsonl"
    diagnostics.spool_payload(spool, {"run_id": "r1"})
    diagnostics.spool_payload(spool, {"run_id": "r2"})
    sent = diagnostics.flush_spool(spool, diagnostics.DiagnosticsClient(
        "https://app.aivanov.eu", session=_FakeSession(status_code=200)))
    assert sent == 2 and not spool.exists()


def test_tab_send_diagnostics_calls_reporter():
    import threading
    from gui_v6.tabs.tab_usage import UsageTab

    tab = object.__new__(UsageTab)  # pas de Tk : on teste juste le helper
    seen = {}
    done = threading.Event()

    def reporter(summary):
        seen["summary"] = summary
        done.set()

    tab._diag_reporter = reporter
    tab._send_diagnostics(SimpleNamespace(documents=[], failed=0))
    assert done.wait(timeout=2.0)
    assert seen["summary"] is not None


def test_tab_send_diagnostics_noop_without_reporter():
    from gui_v6.tabs.tab_usage import UsageTab

    tab = object.__new__(UsageTab)
    tab._diag_reporter = None
    tab._send_diagnostics(SimpleNamespace(documents=[]))  # ne lève pas
