from __future__ import annotations

import json
import sqlite3
from datetime import date
from pathlib import Path

import pytest

from dashboard_v5.comparison_contract import CONTRACT_VERSION, build, catalog


def connection() -> sqlite3.Connection:
    db = sqlite3.connect(":memory:")
    db.row_factory = sqlite3.Row
    db.executescript(
        """
        CREATE TABLE nutrition_daily_summary_v2(
          datum TEXT PRIMARY KEY,kcal REAL,protein_g REAL,carb_g REAL,fat_g REAL,
          item_count INTEGER,nutrient_json TEXT,histamine_score REAL,
          histamine_max INTEGER,histamine_unknown_count INTEGER,histamine_label TEXT,
          updated_at TEXT
        );
        CREATE TABLE capture_entries(
          id INTEGER PRIMARY KEY,root_id TEXT,version INTEGER,capture_type TEXT,
          occurred_at TEXT,ended_at TEXT,payload_json TEXT,status TEXT,
          corrects_entry_id INTEGER,withdraws_entry_id INTEGER,source TEXT,created_at TEXT
        );
        CREATE TABLE medication_administrations(
          id INTEGER PRIMARY KEY,datum TEXT,medication_name TEXT,dose TEXT,route TEXT,
          event_type TEXT,scheduled_next_date TEXT,notes TEXT,source TEXT,
          created_at TEXT,occurred_at TEXT
        );
        CREATE TABLE health_events(
          id INTEGER PRIMARY KEY,date TEXT,category TEXT,parameter TEXT,value REAL,
          unit TEXT,source TEXT,notes TEXT,created_at TEXT,occurred_at TEXT,intensity REAL
        );
        CREATE TABLE symptom_log(
          id INTEGER PRIMARY KEY,datum TEXT,symptom TEXT,schwergrad REAL,kontext TEXT,
          notizen TEXT,created_at TEXT,occurred_at TEXT,onset_at TEXT,duration_minutes INTEGER
        );
        """
    )
    return db


def fake_loader(metric_id: str, start: date, end: date) -> dict:
    definitions = {
        "apple.sleep": ("Gesamtschlaf", "h", [("2026-06-01", 7.0), ("2026-06-03", 6.5)]),
        "apple.resting_heart_rate": ("Ruhepuls", "bpm", [("2026-06-01", 55), ("2026-06-02", 56)]),
        "apple.blood_pressure.systolic": ("Blutdruck systolisch", "mmHg", [("2026-06-01", 120)]),
        "apple.blood_pressure.diastolic": ("Blutdruck diastolisch", "mmHg", [("2026-06-01", 80)]),
    }
    label, unit, points = definitions[metric_id]
    return {
        "label": label,
        "unit": unit,
        "source": {"type": "apple_health"},
        "aggregation_rule": {"id": "daily_metric_points"},
        "coverage": {"observed_days": len(points), "expected_days": (end - start).days + 1},
        "points": [{"date": day, "value": value} for day, value in points if start.isoformat() <= day <= end.isoformat()],
    }


def test_catalog_is_bounded_versioned_and_non_causal():
    result = catalog({metric_id: 2 for metric_id, _ in ((item["id"], item["label"]) for item in catalog()["health_metrics"])})
    assert result["contract_version"] == CONTRACT_VERSION
    assert result["timezone"] == "Europe/Zurich"
    assert result["limits"] == {"health_series": 2, "influences": 2, "days": 366}
    assert len(result["presets"]) == 3
    assert all(len(item["metrics"]) <= 2 and len(item["influences"]) <= 2 for item in result["presets"])
    public = json.dumps(result, ensure_ascii=False).casefold()
    assert "ursache" in public
    assert "recovery-score" not in public
    assert "stress-score" not in public


def test_nutrition_profile_and_histamine_mapping_are_transparent_and_missing_days_stay_unknown():
    db = connection()
    db.executemany(
        "INSERT INTO nutrition_daily_summary_v2 VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
        [
            ("2026-06-01", 2000, 100, 200, 70, 4, "{}", None, 2, 1, "", "2026-06-02"),
            ("2026-06-03", 1800, 90, 180, 60, 3, "{}", None, 1, 0, "", "2026-06-04"),
        ],
    )
    result = build(
        db,
        {"metrics": "apple.sleep", "influences": "nutrition.profile,nutrition.histamine", "from": "2026-06-01", "to": "2026-06-03"},
        fake_loader,
    )
    assert result["contract_version"] == CONTRACT_VERSION
    assert len(result["days"]) == 3
    assert result["days"][1]["values"][0]["data_status"] == "missing"
    assert not any(point["date"] == "2026-06-02" for point in result["health_series"][0]["points"])
    assert {item["id"] for item in result["nutrition_components"]} == {
        "nutrition.energy", "nutrition.protein", "nutrition.carbohydrate", "nutrition.fat", "nutrition.meal_items"
    }
    histamine = [item for item in result["events"] if item["influence_id"] == "nutrition.histamine"]
    assert len(histamine) == 2
    assert histamine[0]["review_status"] == "review_open"
    assert histamine[0]["mapping_coverage"] == pytest.approx(0.75)
    assert "histaminspiegel" not in json.dumps(result, ensure_ascii=False).casefold()
    assert "persönliche verträglichkeit" not in json.dumps(result, ensure_ascii=False).casefold()


def test_zurich_day_boundary_and_following_day_values_for_manual_training():
    db = connection()
    payload = {
        "event_kind": "training", "title": "Training", "note": "", "activity_type": "Gehen",
        "duration_minutes": 45, "active_kcal": None, "distance_km": None,
    }
    db.execute(
        """INSERT INTO capture_entries(
               id,root_id,version,capture_type,occurred_at,ended_at,payload_json,
               status,source,created_at
             ) VALUES(1,'root',1,'event',?,?,?,?,?,?)""",
        ("2026-03-29T22:30:00+00:00", "2026-03-29T23:15:00+00:00", json.dumps(payload), "active", "dashboard_v5_manual_capture", "2026-03-29T22:31:00+00:00"),
    )

    def loader(metric_id: str, start: date, end: date) -> dict:
        return {
            "label": "Gesamtschlaf", "unit": "h", "source": {"type": "apple_health"},
            "aggregation_rule": {"id": "daily_metric_points"}, "coverage": {"observed_days": 1, "expected_days": 2},
            "points": [{"date": "2026-03-31", "value": 7.1}],
        }

    result = build(db, {"metrics": "apple.sleep", "influences": "event.training", "from": "2026-03-30", "to": "2026-03-31"}, loader)
    assert len(result["events"]) == 1
    event = result["events"][0]
    assert event["date"] == "2026-03-30"
    assert event["time"] == "00:30"
    assert event["follow_up_values"] == [{"date": "2026-03-31", "label": "Gesamtschlaf", "value": 7.1, "unit": "h", "source": "Apple Health"}]


def test_medication_lane_includes_only_affirmative_administrations():
    db = connection()
    rows = [
        (1, "2026-06-01", "Medikament A", "1", "subkutan", "verabreicht", None, None, "manual", "2026-06-01", "2026-06-01T08:00:00+02:00"),
        (2, "2026-06-02", "Medikament A", "1", "subkutan", "Dosisreduktion gemäss Arzt/Rezept", None, None, "manual", "2026-06-02", "2026-06-02T08:00:00+02:00"),
    ]
    db.executemany("INSERT INTO medication_administrations VALUES(?,?,?,?,?,?,?,?,?,?,?)", rows)
    result = build(db, {"metrics": "apple.sleep", "influences": "event.medication", "from": "2026-06-01", "to": "2026-06-03"}, fake_loader)
    medications = [item for item in result["events"] if item["influence_id"] == "event.medication"]
    assert len(medications) == 1
    assert medications[0]["date"] == "2026-06-01"


def test_partial_series_failure_keeps_successful_track_and_emits_targeted_error():
    db = connection()

    def loader(metric_id: str, start: date, end: date) -> dict:
        if metric_id == "apple.resting_heart_rate":
            raise RuntimeError("synthetic")
        return fake_loader(metric_id, start, end)

    result = build(db, {"metrics": "apple.sleep,apple.resting_heart_rate", "influences": "", "from": "2026-06-01", "to": "2026-06-03"}, loader)
    assert result["partial"] is True
    assert [item["id"] for item in result["health_series"]] == ["apple.sleep"]
    assert result["component_errors"] == [{"kind": "health_series", "id": "apple.resting_heart_rate", "code": "series_load_failed"}]
    assert len(result["days"]) == 3


def test_units_remain_separate_and_blood_pressure_pair_keeps_common_unit():
    db = connection()
    split = build(db, {"metrics": "apple.sleep,apple.resting_heart_rate", "influences": "", "from": "2026-06-01", "to": "2026-06-03"}, fake_loader)
    assert [item["unit"] for item in split["health_series"]] == ["h", "bpm"]
    pressure = build(db, {"metrics": "apple.blood_pressure.systolic,apple.blood_pressure.diastolic", "influences": "", "from": "2026-06-01", "to": "2026-06-03"}, fake_loader)
    assert {item["unit"] for item in pressure["health_series"]} == {"mmHg"}


def test_selection_bounds_are_enforced_without_raw_error_details():
    db = connection()
    with pytest.raises(ValueError, match="comparison_selection_invalid"):
        build(db, {"metrics": "apple.sleep,apple.hrv,apple.resting_heart_rate", "influences": "", "from": "2026-06-01", "to": "2026-06-03"}, fake_loader)
    with pytest.raises(ValueError, match="comparison_selection_invalid"):
        build(db, {"metrics": "apple.sleep", "influences": "event.training,event.sauna,event.medication", "from": "2026-06-01", "to": "2026-06-03"}, fake_loader)


def test_accessible_ui_contract_is_present_and_primary_copy_has_no_statistics():
    root = Path(__file__).resolve().parents[1]
    render_source = (root / "scripts/health/dashboard_v5/render.py").read_text(encoding="utf-8")
    script = (root / "scripts/health/assets/health-assets/dashboard-v5-associations.js").read_text(encoding="utf-8")
    assert "data-comparison-table" in render_source
    assert "data-comparison-event-dialog" in render_source
    assert "Vergleich zurücksetzen" in render_source
    assert "data-comparison-retry" in render_source
    assert "health.explorer_comparison.v1" not in script  # version is server-owned
    primary_markup = render_source.split("data-explorer-area='associations'", 1)[1].split("{observation_markup}", 1)[0].casefold()
    assert "rangkorrelation" not in primary_markup
    assert "streudiagramm" not in primary_markup
    assert "heatmap" not in primary_markup
