from __future__ import annotations

import sqlite3
import subprocess
import sys
from pathlib import Path

import pytest

ROOT = Path(__file__).resolve().parents[1]


def _api():
    sys.path.insert(0, str(ROOT / "scripts" / "health"))
    from dashboard_v5.read_api import APIError, dispatch_api

    return APIError, dispatch_api


def _build_fixture(path: Path) -> None:
    sys.path.insert(0, str(ROOT / "tests"))
    from fixtures.dashboard_v5_fixture import build_dashboard_v5_fixture

    build_dashboard_v5_fixture(path)


APIError, dispatch_api = _api()


def fixture(tmp_path):
    path = tmp_path / "record.db"
    _build_fixture(path)
    connection = sqlite3.connect(path)
    connection.execute(
        "INSERT INTO dokumente(datei_name,dateipfad,daten_typ,kategorie,institution,review_status,document_date,extrahierte_inhalte,groessekbytes,datei_hash) VALUES(?,?,?,?,?,?,?,?,?,?)",
        (
            "synthetic.pdf",
            "/tmp/not-exposed.pdf",
            "pdf",
            "Labor",
            "Synthetic Clinic",
            "geprueft",
            "2026-05-01",
            "CRP synthetic <img src=x onerror=alert(1)> text",
            12,
            "synthetic-hash",
        ),
    )
    connection.execute(
        "INSERT INTO dokumente(datei_name,dateipfad,daten_typ,kategorie,institution,review_status,document_date,extrahierte_inhalte) VALUES(?,?,?,?,?,?,?,?)",
        (
            "unreviewed.pdf",
            "/tmp/not-exposed.pdf",
            "pdf",
            "Labor",
            "Synthetic Clinic",
            "nicht_geprueft",
            "2026-05-02",
            "CRP must not index",
        ),
    )
    connection.commit()
    connection.close()
    return path


def migrate(path, mode):
    return subprocess.run(
        [
            sys.executable,
            str(ROOT / "scripts" / "health" / "document_fts_migrate.py"),
            "--db",
            str(path),
            mode,
        ],
        capture_output=True,
        text=True,
        check=True,
    )


def test_fts_rebuild_repeat_drop_and_review_gate(tmp_path):
    path = fixture(tmp_path)
    assert "chunks=1" in migrate(path, "--rebuild").stdout
    assert "chunks=1" in migrate(path, "--rebuild").stdout
    assert "fts_check=ok" in migrate(path, "--check").stdout
    docs = dispatch_api(path, "/api/v1/documents", "q=CRP")
    assert len(docs["documents"]) == 1
    opaque = docs["documents"][0]["id"]
    detail = dispatch_api(path, f"/api/v1/documents/{opaque}", "")
    assert "<img" in detail["sections"][0]["text"]
    matches = dispatch_api(path, f"/api/v1/documents/{opaque}/matches", "q=CRP")
    assert matches["matches"][0]["start"] >= 0
    with pytest.raises(APIError):
        dispatch_api(path, "/api/v1/documents", "q=CRP%20OR%20x")
    migrate(path, "--drop")
    with pytest.raises(APIError):
        dispatch_api(path, "/api/v1/documents", "q=CRP")


def test_record_contracts_keep_opaque_ids_and_separate_statuses(tmp_path):
    path = fixture(tmp_path)
    migrate(path, "--rebuild")
    for route in (
        "/api/v1/record-summary",
        "/api/v1/record-labs",
        "/api/v1/medications",
        "/api/v1/appointments",
    ):
        payload = dispatch_api(path, route, "")
        assert isinstance(payload, dict)
    medication = dispatch_api(path, "/api/v1/medications", "")
    assert medication["administered"] and medication["planned"]
    assert {"planned", "administered", "missed", "corrected"} <= set(medication)


def test_fts_current_review_gate_and_literal_operators(tmp_path):
    path = fixture(tmp_path)
    migrate(path, "--rebuild")
    found = dispatch_api(path, "/api/v1/documents", "q=CRP")
    assert found["documents"] and found["documents"][0]["snippet"]
    opaque = found["documents"][0]["id"]
    connection = __import__("sqlite3").connect(path)
    connection.execute(
        "UPDATE dokumente SET review_status='nicht_geprueft' WHERE document_date='2026-05-01'"
    )
    connection.commit()
    connection.close()
    assert not dispatch_api(path, "/api/v1/documents", "q=CRP")["documents"]
    with pytest.raises(APIError) as detail_error:
        dispatch_api(path, f"/api/v1/documents/{opaque}/matches", "q=CRP")
    assert detail_error.value.status == 404
    for operator in ("CRP OR x", "CRP NOT x", "CRP NEAR x", "CRP AND x"):
        with pytest.raises(APIError) as error:
            dispatch_api(path, "/api/v1/documents", f"q={operator.replace(' ', '%20')}")
        assert error.value.status == 400


def test_document_keyset_cursor_is_bound_and_complete(tmp_path):
    path = fixture(tmp_path)
    connection = sqlite3.connect(path)
    rows = [
        (
            f"synthetic-{index}.pdf",
            "/tmp/not-exposed.pdf",
            "pdf" if index % 2 else "image",
            f"gRoUp {index % 3}" if index % 2 else f"Group {index % 3}",
            f"cLINic {index % 4}" if index % 2 else f"Clinic {index % 4}",
            "geprueft",
            "2026-04-15",
            (
                ("needle Übergröße Straße " + "x" * 13000) * 3
                if index % 5 == 0
                else "needle Übergröße Straße"
            ),
            f"2026-05-{(index % 20) + 1:02d}",
        )
        for index in range(160)
    ]
    connection.executemany(
        """INSERT INTO dokumente(
               datei_name,dateipfad,daten_typ,kategorie,institution,review_status,
               document_date,extrahierte_inhalte,upload_datum
           ) VALUES(?,?,?,?,?,?,?,?,?)""",
        rows,
    )
    connection.commit()
    connection.close()
    migrate(path, "--rebuild")
    for sort in (
        "document_date_desc",
        "document_date_asc",
        "import_date_desc",
        "category",
        "institution",
        "type",
        "review_status",
    ):
        seen: list[str] = []
        cursor = None
        while True:
            query = f"sort={sort}&limit=23"
            if cursor:
                query += f"&cursor={cursor}"
            page = dispatch_api(path, "/api/v1/documents", query)
            seen.extend(item["id"] for item in page["documents"])
            assert page["truncated"] is bool(page["next_cursor"])
            cursor = page["next_cursor"]
            if not cursor:
                break
        assert len(seen) == 162
        assert len(set(seen)) == len(seen)
    fts_seen: list[str] = []
    cursor = None
    while True:
        fts_query = "q=needle&limit=19"
        if cursor:
            fts_query += f"&cursor={cursor}"
        page = dispatch_api(path, "/api/v1/documents", fts_query)
        fts_seen.extend(item["id"] for item in page["documents"])
        cursor = page["next_cursor"]
        if not cursor:
            break
    assert len(fts_seen) == 160
    assert len(set(fts_seen)) == len(fts_seen)
    global_search = dispatch_api(path, "/api/v1/search", "q=needle")
    global_ids = [item["id"] for item in global_search["groups"]["documents"]]
    assert len(global_ids) == len(set(global_ids))
    first = dispatch_api(path, "/api/v1/documents", "q=needle&limit=19")
    for bad_query in (
        f"sort=category&cursor={first['next_cursor']}",
        f"q=other&cursor={first['next_cursor']}",
        f"q=needle&cursor={first['next_cursor']}x",
    ):
        with pytest.raises(APIError) as error:
            dispatch_api(path, "/api/v1/documents", bad_query)
        assert error.value.status == 400
    assert "needle" not in first["next_cursor"].casefold()
    assert "/tmp" not in first["next_cursor"]


def test_future_planning_and_selectable_doctor_report(tmp_path):
    path = fixture(tmp_path)
    connection = sqlite3.connect(path)
    connection.executemany(
        """INSERT INTO medication_administrations(
               datum,medication_name,dose,route,event_type,scheduled_next_date,source
           ) VALUES(?,?,?,?,?,?,?)""",
        [
            (
                "2026-07-01",
                "Future A",
                "1",
                "oral",
                "planned",
                "2099-01-02",
                "synthetic",
            ),
            (
                "2026-07-01",
                "Future B",
                "1",
                "oral",
                "scheduled",
                "2099-01-01",
                "synthetic",
            ),
            ("2020-01-01", "Past plan", "1", "oral", "planned", None, "synthetic"),
            (
                "2026-07-01",
                "Given",
                "1",
                "oral",
                "administered",
                "2099-02-01",
                "synthetic",
            ),
            ("2026-07-01", "Missed", "1", "oral", "missed", None, "synthetic"),
            ("2026-07-01", "Corrected", "1", "oral", "corrected", None, "synthetic"),
        ],
    )
    connection.commit()
    connection.close()
    medications = dispatch_api(path, "/api/v1/medications", "")
    future = [
        item for item in medications["planned"] if item["name"].startswith("Future")
    ]
    assert [item["name"] for item in future] == ["Future B", "Future A"]
    assert all(item["scheduled_next_date"] == item["date"] for item in future)
    assert any(item["name"] == "Past plan" for item in medications["planned"])
    assert any(item["name"] == "Given" for item in medications["administered"])
    assert not any(item["name"] == "Given" for item in medications["planned"])
    summary = dispatch_api(path, "/api/v1/record-summary", "")
    assert [item["name"] for item in summary["next_planned"]] == [
        "Future B",
        "Future A",
    ]
    report = dispatch_api(
        path,
        "/api/v1/doctor-report",
        "from=2026-01-01&to=2026-07-15&sections=medications,documents",
    )
    assert report["selected_sections"] == ["documents", "medications"]
    assert report["overview"] is None and report["labs"] == []
    assert report["medical_statement"].startswith("Dokumentierte Daten")
    with pytest.raises(APIError) as error:
        dispatch_api(
            path,
            "/api/v1/doctor-report",
            "from=2026-01-01&to=2026-07-15&sections=diagnosis",
        )
    assert error.value.status == 400


def test_lab_chronology_report_range_and_next_planned_scaling(tmp_path):
    path = fixture(tmp_path)
    connection = sqlite3.connect(path)
    connection.executemany(
        """INSERT INTO laborwerte(
               parameter_name,wert,einheit,reference_min,reference_max,
               abnahme_datum,befund_datum,wert_original,quelle,validierungsstatus,
               source_type,reference_range_source,verified_against_original,
               provenance_note,ermittlung_datum
           ) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
        [
            (
                "C-Reaktives Protein (CRP)",
                str(value),
                "mg/L",
                "0",
                "5",
                day,
                day,
                str(value),
                "synthetic",
                "validiert",
                "synthetic",
                "scanned_original",
                1,
                "synthetic-only",
                "2026-07-15T12:00:00+00:00",
            )
            for day, value in (
                ("2026-06-01", 1),
                ("2026-06-02", 2),
                ("2026-06-03", 4),
            )
        ],
    )
    connection.executemany(
        """INSERT INTO medication_administrations(
               datum,medication_name,dose,route,event_type,scheduled_next_date,source
           ) VALUES(?,?,?,?,?,?,?)""",
        [
            (
                "2026-07-14",
                f"Recent administered {index}",
                "1",
                "oral",
                "administered",
                None,
                "synthetic",
            )
            for index in range(130)
        ]
        + [
            (
                "2020-01-01",
                "Future retained plan",
                "1",
                "oral",
                "planned",
                "2099-01-01",
                "synthetic",
            )
        ],
    )
    connection.commit()
    connection.close()

    labs = dispatch_api(path, "/api/v1/record-labs", "q=CRP")
    newest = next(item for item in labs["observations"] if item["date"] == "2026-06-03")
    assert newest["value"] == 4
    assert newest["previous"] == 2
    assert newest["absolute_change"] == 2

    report = dispatch_api(
        path,
        "/api/v1/doctor-report",
        "from=2026-06-02&to=2026-06-03&sections=labs",
    )
    assert [item["date"] for item in report["labs"]] == ["2026-06-03", "2026-06-02"]
    assert report["completeness"]["labs"]["truncated"] is False

    summary = dispatch_api(path, "/api/v1/record-summary", "")
    assert summary["next_planned"][0]["name"] == "Future retained plan"
