from __future__ import annotations

import importlib.util
import sqlite3
import sys
from datetime import date
from pathlib import Path

import pytest

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts" / "health"))

from dashboard_v5.nutrition_mapping_review import build_review  # noqa: E402
from dashboard_v5.comparison_contract import _histamine_events  # noqa: E402

fixture_spec = importlib.util.spec_from_file_location(
    "dashboard_v5_fixture_7cd", ROOT / "tests" / "fixtures" / "dashboard_v5_fixture.py"
)
assert fixture_spec and fixture_spec.loader
fixture_module = importlib.util.module_from_spec(fixture_spec)
fixture_spec.loader.exec_module(fixture_module)
build_fixture = fixture_module.build_dashboard_v5_fixture

TODAY = date(2026, 6, 15)
ALIAS = "SYNTHETIC_INCOMPLETE_FOOD_MAPPING_WITH_A_VERY_LONG_NAME_THAT_MUST_WRAP"


@pytest.fixture()
def fixture_db(tmp_path: Path) -> Path:
    db = tmp_path / "fixture.db"
    build_fixture(db, anchor_date=TODAY)
    return db


def preview(db: Path) -> dict[str, object]:
    connection = sqlite3.connect(db)
    connection.row_factory = sqlite3.Row
    try:
        return next(group for group in build_review(connection)["groups"] if group["example_name"] == ALIAS)
    finally:
        connection.close()


def payload(db: Path, decision: str = "assign", **changes: object) -> dict[str, object]:
    group = preview(db)
    assigning = decision == "assign"
    data: dict[str, object] = {
        "version": 2,
        "action": "nutrition_mapping",
        "decision": decision,
        "queue_key": group["queue_key"],
        "alias": ALIAS,
        "canonical_food": "synthetic reviewed food" if assigning else "",
        "sighi_score": 1 if assigning else "unknown",
        "confidence": "medium" if assigning else "low",
        "mapping_method": "manual_review",
        "source_label": "Nutzerprüfung",
        "source_version": "nutrition_mapping_review_v1",
        "note": "synthetic sprint 7c-d action",
        "ingredient_review_required": decision == "composite",
        "personal_tolerance_status": "unknown",
        "personal_tolerance_note": "",
        "target_revision": group["target_revision"],
        "target_count": group["target_count"],
        "target_day_count": group["target_day_count"],
    }
    data.update(changes)
    return data


def state(db: Path) -> str:
    return str(preview(db)["status"])


def test_preview_target_change_fails_before_any_mapping(fixture_db: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    import health_dashboard_action_worker as worker

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    stale = payload(fixture_db)
    connection = sqlite3.connect(fixture_db)
    try:
        connection.execute(
            """INSERT INTO nutrition_items
               (source,source_item_id,datum,meal,name,kcal,item_hash)
               VALUES('fixture','late-target','2026-06-12','snack',?,20,'late-target-hash')""",
            (ALIAS,),
        )
        connection.commit()
    finally:
        connection.close()

    with pytest.raises(RuntimeError, match="preview target changed"):
        worker.apply_mapping_action(stale, "1" * 32)

    connection = sqlite3.connect(fixture_db)
    try:
        assert connection.execute("SELECT COUNT(*) FROM nutrition_mapping_action_log").fetchone()[0] == 0
        assert connection.execute(
            """SELECT COUNT(*) FROM nutrition_histamine_scores h
               JOIN nutrition_items i ON i.id=h.item_id
               WHERE i.name=? AND h.sighi_score IS NOT NULL""",
            (ALIAS,),
        ).fetchone()[0] == 0
    finally:
        connection.close()


def test_defer_reopen_not_assignable_and_irrelevant_are_distinct(fixture_db: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    import health_dashboard_action_worker as worker

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    worker.apply_mapping_action(payload(fixture_db, "defer"), "2" * 32)
    assert state(fixture_db) == "deferred"
    worker.apply_mapping_action(payload(fixture_db, "reopen"), "3" * 32)
    assert state(fixture_db) in {"unassigned", "suggestion_available", "review_required"}
    worker.apply_mapping_action(payload(fixture_db, "not_assignable"), "4" * 32)
    assert state(fixture_db) == "not_assignable"
    worker.apply_mapping_action(payload(fixture_db, "reopen"), "5" * 32)
    worker.apply_mapping_action(payload(fixture_db, "irrelevant"), "6" * 32)
    assert state(fixture_db) == "irrelevant"


def test_group_failure_rolls_back_every_target_and_audit_row(fixture_db: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    import health_dashboard_action_worker as worker

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    connection = sqlite3.connect(fixture_db)
    try:
        cursor = connection.execute(
            """INSERT INTO nutrition_items
               (source,source_item_id,datum,meal,name,kcal,item_hash)
               VALUES('fixture','second-target','2026-06-12','snack',?,20,'second-target-hash')""",
            (ALIAS,),
        )
        blocked_id = int(cursor.lastrowid)
        connection.execute(
            f"""CREATE TRIGGER block_second_mapping BEFORE INSERT ON nutrition_histamine_scores
                WHEN NEW.item_id={blocked_id} BEGIN SELECT RAISE(ABORT,'synthetic group failure'); END"""
        )
        connection.commit()
    finally:
        connection.close()

    with pytest.raises(sqlite3.IntegrityError, match="synthetic group failure"):
        worker.apply_mapping_action(payload(fixture_db), "7" * 32)

    connection = sqlite3.connect(fixture_db)
    try:
        assert connection.execute(
            """SELECT COUNT(*) FROM nutrition_histamine_scores h
               JOIN nutrition_items i ON i.id=h.item_id
               WHERE i.name=? AND h.sighi_score IS NOT NULL""",
            (ALIAS,),
        ).fetchone()[0] == 0
        assert connection.execute("SELECT COUNT(*) FROM nutrition_mapping_action_log").fetchone()[0] == 0
        assert connection.execute("SELECT COUNT(*) FROM nutrition_mapping_provenance").fetchone()[0] == 12
    finally:
        connection.close()


def test_correction_is_preview_bound_and_audited(fixture_db: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    import health_dashboard_action_worker as worker

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    worker.apply_mapping_action(payload(fixture_db), "8" * 32)
    connection = sqlite3.connect(fixture_db)
    try:
        connection.execute(
            """INSERT INTO histamine_food_rules
               (canonical_food,sighi_score,category,confidence,source,updated_at)
               VALUES('synthetic corrected food',2,'synthetic category','medium',
                      'synthetic-catalog','fixture-v2')"""
        )
        connection.commit()
    finally:
        connection.close()
    correction = payload(
        fixture_db,
        canonical_food="synthetic corrected food",
        sighi_score=2,
        note="explicit correction",
    )
    worker.apply_mapping_action(correction, "9" * 32)

    connection = sqlite3.connect(fixture_db)
    try:
        assert connection.execute(
            """SELECT h.canonical_food,h.sighi_score FROM nutrition_histamine_scores h
               JOIN nutrition_items i ON i.id=h.item_id WHERE i.name=?""",
            (ALIAS,),
        ).fetchone() == ("synthetic corrected food", 2)
        assert connection.execute(
            "SELECT COUNT(*) FROM nutrition_mapping_action_log WHERE action_id IN (?,?)",
            ("8" * 32, "9" * 32),
        ).fetchone()[0] == 2
    finally:
        connection.close()


def test_confirmed_mapping_unlocks_only_the_completed_observation_day(
    fixture_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    import health_dashboard_action_worker as worker

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    connection = sqlite3.connect(fixture_db)
    connection.row_factory = sqlite3.Row
    try:
        affected_day = date.fromisoformat(build_review(connection, status="open")["groups"][0]["days"][0])
        before = _histamine_events(
            connection,
            affected_day,
            affected_day,
        )
    finally:
        connection.close()

    worker.apply_mapping_action(payload(fixture_db), "f" * 32)

    connection = sqlite3.connect(fixture_db)
    connection.row_factory = sqlite3.Row
    try:
        after = _histamine_events(
            connection,
            affected_day,
            affected_day,
        )
        assert len(before) == 1
        assert before[0]["review_status"] == "review_open"
        assert len(after) == 1
        assert after[0]["date"] == affected_day.isoformat()
        assert after[0]["review_status"] == "complete_mapping"
        assert after[0]["mapped_entries"] > before[0]["mapped_entries"]
    finally:
        connection.close()


def test_assign_rejects_arbitrary_manual_classification(
    fixture_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    import health_dashboard_action_worker as worker

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    tampered = payload(
        fixture_db, canonical_food="invented medical class", sighi_score=3,
        mapping_method="manual_review", confidence="high",
    )
    with pytest.raises(RuntimeError, match="local reference"):
        worker.apply_mapping_action(tampered, "a1" * 16)
    connection = sqlite3.connect(fixture_db)
    try:
        assert connection.execute("SELECT COUNT(*) FROM nutrition_mapping_action_log").fetchone()[0] == 0
    finally:
        connection.close()


def test_removed_exact_alias_fails_closed_after_preview(
    fixture_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    import health_dashboard_action_worker as worker

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    connection = sqlite3.connect(fixture_db)
    try:
        connection.execute(
            "INSERT INTO histamine_food_aliases(alias,canonical_food) VALUES(?,?)",
            (ALIAS, "synthetic reviewed food"),
        )
        connection.commit()
    finally:
        connection.close()
    stale = payload(fixture_db, mapping_method="local_alias")
    connection = sqlite3.connect(fixture_db)
    try:
        connection.execute("DELETE FROM histamine_food_aliases WHERE alias=?", (ALIAS,))
        connection.commit()
    finally:
        connection.close()
    with pytest.raises(RuntimeError, match="local reference"):
        worker.apply_mapping_action(stale, "a2" * 16)


@pytest.mark.parametrize("mutation", ["collision", "unsafe_source"])
def test_catalog_collision_or_unsafe_provenance_fails_closed_at_apply(
    fixture_db: Path, monkeypatch: pytest.MonkeyPatch, mutation: str
) -> None:
    import health_dashboard_action_worker as worker

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    pending = payload(fixture_db)
    connection = sqlite3.connect(fixture_db)
    try:
        if mutation == "collision":
            connection.execute(
                """INSERT INTO histamine_food_rules
                   (canonical_food,sighi_score,category,confidence,source,updated_at)
                   VALUES('SYNTHETIC REVIEWED FOOD',2,'other','low','collision','2026-01-01')"""
            )
        else:
            connection.execute(
                "UPDATE histamine_food_rules SET source='https://internal.example/source' WHERE canonical_food='synthetic reviewed food'"
            )
        connection.commit()
    finally:
        connection.close()
    with pytest.raises(RuntimeError, match="local reference"):
        worker.apply_mapping_action(pending, ("a6" if mutation == "collision" else "a7") * 16)
    connection = sqlite3.connect(fixture_db)
    try:
        assert connection.execute("SELECT COUNT(*) FROM nutrition_mapping_action_log").fetchone()[0] == 0
    finally:
        connection.close()


def test_state_action_invalidates_an_older_preview(
    fixture_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    import health_dashboard_action_worker as worker

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    stale = payload(fixture_db, "irrelevant")
    worker.apply_mapping_action(payload(fixture_db, "defer"), "a3" * 16)
    with pytest.raises(RuntimeError, match="preview target changed"):
        worker.apply_mapping_action(stale, "a4" * 16)


def test_source_status_uses_the_same_grouped_open_count(
    fixture_db: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    import health_dashboard_action_worker as worker
    from dashboard_v5.source_status import build_source_status

    monkeypatch.setattr(worker, "DASHBOARD_DB", fixture_db)
    connection = sqlite3.connect(fixture_db)
    connection.row_factory = sqlite3.Row
    try:
        before_contract = build_review(connection)["summary"]["open_entries"]
        before_source = next(
            source for source in build_source_status(connection)["sources"]
            if source["source_key"] == "nutrition_yazio"
        )["open_review_count"]
        assert before_source == before_contract
    finally:
        connection.close()
    worker.apply_mapping_action(payload(fixture_db, "irrelevant"), "a5" * 16)
    connection = sqlite3.connect(fixture_db)
    connection.row_factory = sqlite3.Row
    try:
        after_contract = build_review(connection)["summary"]["open_entries"]
        after_source = next(
            source for source in build_source_status(connection)["sources"]
            if source["source_key"] == "nutrition_yazio"
        )["open_review_count"]
        assert after_source == after_contract < before_contract
    finally:
        connection.close()


def test_mapping_api_search_allowlist_and_validation(fixture_db: Path) -> None:
    from dashboard_v5.read_api import APIError, dispatch_api

    result = dispatch_api(
        fixture_db, "/api/v1/nutrition/mapping-queue", "status=all&search=SYNTHETIC"
    )
    assert any(ALIAS in group["example_name"] for group in result["groups"])
    for query in (
        "status=all&search=one&search=two",
        "status=all&search=%00",
        "status=all&search=" + "x" * 121,
    ):
        with pytest.raises(APIError):
            dispatch_api(fixture_db, "/api/v1/nutrition/mapping-queue", query)


def test_origin_scheme_is_bound_to_trusted_server_configuration(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    dashboard = tmp_path / "dashboard.html"
    dashboard.write_text("<html></html>", encoding="utf-8")
    monkeypatch.setenv("HEALTH_DASHBOARD_FILE", str(dashboard))
    monkeypatch.setenv("HEALTH_DASHBOARD_ALLOWED_HOSTS", "127.0.0.1")
    monkeypatch.setenv("HEALTH_DASHBOARD_SCHEME", "http")
    spec = importlib.util.spec_from_file_location(
        "health_dashboard_server_7cd_origin", ROOT / "scripts" / "health" / "health_dashboard_server.py"
    )
    assert spec and spec.loader
    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)
    assert module.origin_matches_request("http://127.0.0.1:8014", "127.0.0.1:8014")
    assert not module.origin_matches_request("https://127.0.0.1:8014", "127.0.0.1:8014")
    assert not module.origin_matches_request("http://127.0.0.1:65535", "127.0.0.1:65535")
    assert not module.origin_matches_request("http://localhost:8014", "localhost:8014")
