from __future__ import annotations

import sqlite3
import sys
import importlib.util
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_contract import (  # noqa: E402
    NUTRIENT_CONTRACT_VERSION,
    normalize_nutrient_status,
)
from dashboard_v5.read_api import _daily_nutrients  # noqa: E402
from dashboard_v5.render import render  # noqa: E402
from dashboard_v5.data_provider import build_bundle, connect  # noqa: E402
from dashboard_v5.sprint6h_a_schema import apply_schema, assert_schema  # noqa: E402
import yazio_nutrition_sync as yazio_sync  # noqa: E402

fixture_spec = importlib.util.spec_from_file_location(
    "dashboard_v5_fixture_6ha", 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_dashboard_v5_fixture = fixture_module.build_dashboard_v5_fixture


def test_explicit_g_to_mg_conversion_is_versioned() -> None:
    result = normalize_nutrient_status("mineral.calcium", 0.15, "g")
    assert result == {
        "status": "documented_value",
        "value": 150.0,
        "unit": "mg",
        "raw_value": 0.15,
        "raw_unit": "g",
        "factor": 1000.0,
        "contract_version": NUTRIENT_CONTRACT_VERSION,
    }


def test_unknown_unit_and_missing_value_fail_closed_not_zero() -> None:
    unknown = normalize_nutrient_status("nutrient.sodium", 0.86, "portion")
    missing = normalize_nutrient_status("nutrient.sodium", None, "g")
    assert unknown["status"] == "unknown" and unknown["value"] is None
    assert missing["status"] == "not_reported" and missing["value"] is None


def test_explicit_zero_stays_zero_but_legacy_default_zero_is_unknown() -> None:
    explicit = normalize_nutrient_status("mineral.chlorine", 0, "g", explicit_zero=True)
    ambiguous = normalize_nutrient_status("mineral.chlorine", 0, "g", explicit_zero=False)
    assert explicit["status"] == "documented_zero" and explicit["value"] == 0
    assert ambiguous["status"] == "unknown" and ambiguous["value"] is None


def test_yazio_default_zero_keeps_raw_provenance_but_is_not_a_numeric_value(
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    def fake_api(path: str, _token: str, _params=None):
        if path == "/user/consumed-items":
            return {"products": [{"product_id": "p1", "amount": 100, "daytime": "lunch"}]}
        return {"name": "synthetic", "nutrients": {"mineral.calcium": 0}}

    monkeypatch.setattr(yazio_sync, "api_get", fake_api)
    item = yazio_sync.fetch_items("2026-06-15", "synthetic-token")[0]
    assert "mineral.calcium" not in item["nutrients"]
    assert item["nutrient_provenance"]["mineral.calcium"]["status"] == "unknown"
    assert item["nutrient_provenance"]["mineral.calcium"]["raw_value"] == 0


def _source_db(path: Path) -> sqlite3.Connection:
    connection = sqlite3.connect(path)
    connection.row_factory = sqlite3.Row
    connection.executescript(
        """
        CREATE TABLE nutrition_items(
          id INTEGER PRIMARY KEY, datum TEXT, amount REAL, amount_unit TEXT, name TEXT
        );
        CREATE TABLE nutrition_item_nutrients(
          item_id INTEGER, nutrient_key TEXT, value REAL, unit TEXT, raw_value TEXT,
          PRIMARY KEY(item_id,nutrient_key)
        );
        INSERT INTO nutrition_items VALUES(1,'2026-06-15',100,'g','synthetic');
        INSERT INTO nutrition_item_nutrients VALUES(1,'mineral.calcium',0.15,'mg','0.15');
        INSERT INTO nutrition_item_nutrients VALUES(1,'mineral.chlorine',0,'mg','0');
        INSERT INTO nutrition_item_nutrients VALUES(1,'nutrient.sodium',NULL,NULL,NULL);
        """
    )
    return connection


def test_api_projection_preserves_statuses_and_separate_yazio_source(tmp_path: Path) -> None:
    connection = _source_db(tmp_path / "source.db")
    try:
        result = _daily_nutrients(connection, date(2026, 6, 15), date(2026, 6, 15))["2026-06-15"]
    finally:
        connection.close()
    assert result["mineral.calcium"]["yazio_value"] == 150
    assert result["mineral.calcium"]["enrichment_value"] is None
    assert result["mineral.calcium"]["data_status"] == "documented_value"
    assert result["mineral.chlorine"]["value"] is None
    assert result["mineral.chlorine"]["data_status"] == "unknown"
    assert result["nutrient.sodium"]["value"] is None
    assert result["nutrient.sodium"]["data_status"] == "not_reported"


def test_additive_schema_is_idempotent_and_preserves_raw_rows(tmp_path: Path) -> None:
    connection = _source_db(tmp_path / "migration.db")
    try:
        before = tuple(connection.execute(
            "SELECT item_id,nutrient_key,value,unit,raw_value FROM nutrition_item_nutrients ORDER BY nutrient_key"
        ))
        assert apply_schema(connection) == 5
        connection.commit()
        assert apply_schema(connection) == 0
        assert_schema(connection)
        after = tuple(connection.execute(
            "SELECT item_id,nutrient_key,value,unit,raw_value FROM nutrition_item_nutrients ORDER BY nutrient_key"
        ))
        assert before == after
        assert connection.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
    finally:
        connection.close()


def test_render_contract_has_visual_hierarchy_and_collapsed_details(tmp_path: Path) -> None:
    database = tmp_path / "render.db"
    build_dashboard_v5_fixture(database, anchor_date=date(2026, 6, 15))
    connection = connect(database)
    try:
        bundle = build_bundle(connection, today="2026-06-15")
    finally:
        connection.close()
    html = render(bundle, health_record_6e=True)
    assert "Gesundheitscockpit" in html
    assert "Priorisierte Gesundheitswerte" in html and "Weitere Gesundheitswerte" in html
    assert "Datentabelle, Ereignisse und Methodik" in html
    assert "cockpit-chart-frame" in html
    assert "dashboard-v5-nutrition.js" in html
    assert "Normalbereich" not in html
