from __future__ import annotations

import importlib.util
import ast
import hashlib
import json
import sqlite3
import sys
from pathlib import Path
from unittest import mock

import pytest

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


def load_module(name: str, relative: str):
    path = ROOT / relative
    spec = importlib.util.spec_from_file_location(name, path)
    assert spec is not None and spec.loader is not None
    module = importlib.util.module_from_spec(spec)
    sys.modules[name] = module
    spec.loader.exec_module(module)
    return module


def test_gog_secret_loaded_from_protected_env_file(tmp_path, monkeypatch):
    mod = load_module("apple_health_drive_sync_test", "scripts/health/apple_health_drive_sync.py")
    secret_file = tmp_path / "gog_keyring.env"
    fixture_value = "runtime-" + "fixture"
    secret_file.write_text("export GOG_KEYRING_PASSWORD='" + fixture_value + "'\n", encoding="utf-8")
    secret_file.chmod(0o600)
    monkeypatch.delenv("GOG_KEYRING_PASSWORD", raising=False)
    monkeypatch.setattr(mod, "GOG_SECRET_ENV", secret_file)

    assert mod.load_gog_keyring_password() == fixture_value


def test_gog_secret_missing_fails_closed(tmp_path, monkeypatch):
    mod = load_module("apple_health_drive_sync_missing_test", "scripts/health/apple_health_drive_sync.py")
    monkeypatch.delenv("GOG_KEYRING_PASSWORD", raising=False)
    monkeypatch.setattr(mod, "GOG_SECRET_ENV", tmp_path / "missing.env")

    with pytest.raises(RuntimeError, match="Missing gog keyring secret"):
        mod.load_gog_keyring_password()


def setup_importer(tmp_path, monkeypatch):
    mod = load_module("apple_health_import_test", "scripts/health/apple_health_import.py")
    db = tmp_path / "health.db"
    processed = tmp_path / "processed"
    monkeypatch.setattr(mod, "DB", db)
    monkeypatch.setattr(mod, "PROCESSED", processed)
    return mod, db, processed


def test_import_is_atomic_and_error_file_can_be_retried(tmp_path, monkeypatch):
    mod, db, _ = setup_importer(tmp_path, monkeypatch)
    source = tmp_path / "export.json"
    source.write_text(json.dumps([{"metric": "step_count", "date": "2026-01-01", "qty": 1}, {"metric": "step_count", "date": "2026-01-02", "qty": 2}]), encoding="utf-8")

    original = mod.normalize_record
    calls = 0

    def fail_second(record, file_name, file_hash):
        nonlocal calls
        calls += 1
        if calls == 2:
            raise RuntimeError("synthetic parse failure")
        return original(record, file_name, file_hash)

    with mock.patch.object(mod, "normalize_record", side_effect=fail_second):
        with pytest.raises(RuntimeError, match="synthetic parse failure"):
            mod.import_file(source)

    con = sqlite3.connect(db)
    assert con.execute("SELECT count(*) FROM apple_health_records").fetchone()[0] == 0
    assert con.execute("SELECT status FROM apple_health_import_files").fetchone()[0] == "error"
    con.close()

    result = mod.import_file(source)
    assert result["status"] == "imported"
    con = sqlite3.connect(db)
    assert con.execute("SELECT count(*) FROM apple_health_records").fetchone()[0] == 2
    assert con.execute("SELECT status FROM apple_health_import_files").fetchone()[0] == "imported"
    assert con.execute("SELECT retry_count FROM apple_health_import_files").fetchone()[0] == 1
    con.close()


def test_move_failure_rolls_back_database_import(tmp_path, monkeypatch):
    mod, db, _ = setup_importer(tmp_path, monkeypatch)
    source = tmp_path / "export.json"
    source.write_text(json.dumps([{"metric": "step_count", "date": "2026-01-01", "qty": 1}]), encoding="utf-8")

    with mock.patch.object(mod, "archive_file", side_effect=OSError("synthetic archive failure")):
        with pytest.raises(OSError, match="synthetic archive failure"):
            mod.import_file(source, move=True)

    con = sqlite3.connect(db)
    assert con.execute("SELECT count(*) FROM apple_health_records").fetchone()[0] == 0
    assert con.execute("SELECT status FROM apple_health_import_files").fetchone()[0] == "error"
    con.close()
    assert source.exists()


def test_commit_failure_after_archive_restores_source_and_rolls_back(tmp_path, monkeypatch):
    mod, db, processed = setup_importer(tmp_path, monkeypatch)
    source = tmp_path / "export.json"
    source.write_text(json.dumps([{"metric": "step_count", "date": "2026-01-01", "qty": 1}]), encoding="utf-8")
    real = sqlite3.connect(db)
    real.row_factory = sqlite3.Row

    class CommitFailConnection:
        def __init__(self, connection):
            self.connection = connection
            self.commit_count = 0

        def __getattr__(self, name):
            return getattr(self.connection, name)

        def commit(self):
            self.commit_count += 1
            if self.commit_count == 2:
                raise sqlite3.OperationalError("synthetic final commit failure")
            return self.connection.commit()

    wrapped = CommitFailConnection(real)
    monkeypatch.setattr(mod, "con", lambda: wrapped)
    with pytest.raises(sqlite3.OperationalError, match="synthetic final commit failure"):
        mod.import_file(source, move=True)

    assert source.exists()
    assert list(processed.glob("*_export.json")) == []
    check = sqlite3.connect(db)
    assert check.execute("SELECT count(*) FROM apple_health_records").fetchone()[0] == 0
    assert check.execute("SELECT status FROM apple_health_import_files").fetchone()[0] == "error"
    check.close()


def test_non_duplicate_integrity_error_rolls_back_complete_file(tmp_path, monkeypatch):
    mod, db, _ = setup_importer(tmp_path, monkeypatch)
    source = tmp_path / "export.json"
    source.write_text(json.dumps([{"metric": "step_count", "date": "2026-01-01", "qty": 1}]), encoding="utf-8")
    original = mod.normalize_record

    def invalid_record(record, file_name, file_hash):
        normalized = original(record, file_name, file_hash)
        normalized["raw_json"] = None
        return normalized

    with mock.patch.object(mod, "normalize_record", side_effect=invalid_record):
        with pytest.raises(sqlite3.IntegrityError):
            mod.import_file(source)

    con = sqlite3.connect(db)
    assert con.execute("SELECT count(*) FROM apple_health_records").fetchone()[0] == 0
    assert con.execute("SELECT status FROM apple_health_import_files").fetchone()[0] == "error"
    con.close()


def test_already_imported_file_is_archived_when_move_requested(tmp_path, monkeypatch):
    mod, _, processed = setup_importer(tmp_path, monkeypatch)
    source = tmp_path / "export.json"
    source.write_text(json.dumps([{"metric": "step_count", "date": "2026-01-01", "qty": 1}]), encoding="utf-8")
    assert mod.import_file(source)["status"] == "imported"

    result = mod.import_file(source, move=True)

    assert result["status"] == "already_imported"
    assert not source.exists()
    assert len(list(processed.glob("*_export.json"))) == 1


def test_schema_is_reproducible_and_matches_complete_structure_manifest(tmp_path):
    schema = (ROOT / "database/schema.sql").read_text(encoding="utf-8")
    expected = json.loads((ROOT / "database/schema_manifest.json").read_text(encoding="utf-8"))
    con = sqlite3.connect(tmp_path / "schema.db")
    con.executescript(schema)
    tables = {}
    for (name,) in con.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"):
        tables[name] = [row[1] for row in con.execute(f'PRAGMA table_info("{name}")')]
    canonical = json.dumps(tables, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode()
    assert len(tables) == expected["table_count"]
    assert hashlib.sha256(canonical).hexdigest() == expected["digest"]
    assert con.execute("PRAGMA integrity_check").fetchone()[0] == "ok"
    assert con.execute("PRAGMA foreign_key_check").fetchall() == []
    con.close()


def test_no_gog_secret_default_is_embedded_in_sync_source():
    source = (ROOT / "scripts/health/apple_health_drive_sync.py").read_text(encoding="utf-8")
    forbidden_default = "setdefault(" + "\"GOG_KEYRING_PASSWORD\""
    assert forbidden_default not in source
    assert "env[\"GOG_KEYRING_PASSWORD\"] = load_gog_keyring_password()" in source


def test_no_health_script_embeds_a_gog_password_literal():
    violations = []
    for path in (ROOT / "scripts").rglob("*.py"):
        tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
        for node in ast.walk(tree):
            if isinstance(node, ast.Dict):
                for key, value in zip(node.keys, node.values):
                    if (
                        isinstance(key, ast.Constant)
                        and key.value == "GOG_KEYRING_PASSWORD"
                        and isinstance(value, ast.Constant)
                        and isinstance(value.value, str)
                        and value.value
                    ):
                        violations.append(f"{path}:{node.lineno}")
            if isinstance(node, (ast.Assign, ast.AnnAssign)):
                targets = node.targets if isinstance(node, ast.Assign) else [node.target]
                value = node.value
                if (
                    any(isinstance(target, ast.Name) and target.id == "GOG_KEYRING_PASSWORD" for target in targets)
                    and isinstance(value, ast.Constant)
                    and isinstance(value.value, str)
                    and value.value
                ):
                    violations.append(f"{path}:{node.lineno}")
            if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute) and node.func.attr == "setdefault":
                if node.args and isinstance(node.args[0], ast.Constant) and node.args[0].value == "GOG_KEYRING_PASSWORD":
                    violations.append(f"{path}:{node.lineno}")
    assert violations == []


def test_all_cron_gog_consumers_fail_closed_on_empty_secret(tmp_path, monkeypatch):
    monkeypatch.delenv("GOG_KEYRING_PASSWORD", raising=False)
    empty = tmp_path / "gog_keyring.env"
    empty.write_text("GOG_KEYRING_PASSWORD=''\n", encoding="utf-8")
    empty.chmod(0o600)
    for index, relative in enumerate((
        "scripts/cron/morning_briefing.py",
        "scripts/cron/weekly_health_drive_backup.py",
        "scripts/cron/health_daily_sync.py",
    )):
        mod = load_module(f"cron_gog_fail_closed_{index}", relative)
        monkeypatch.setattr(mod, "GOG_SECRET_ENV", empty)
        with pytest.raises(RuntimeError, match="empty"):
            mod.load_gog_keyring_password()
        if relative.endswith("health_daily_sync.py"):
            with mock.patch.object(mod.subprocess, "run") as subprocess_run:
                with pytest.raises(RuntimeError, match="empty"):
                    mod.run(["gog", "gmail", "messages"])
                subprocess_run.assert_not_called()


def test_file_based_gog_secrets_reject_group_or_world_permissions(tmp_path, monkeypatch):
    monkeypatch.delenv("GOG_KEYRING_PASSWORD", raising=False)
    unsafe = tmp_path / "gog_keyring.env"
    fixture_value = "fixture-" + "value"
    unsafe.write_text("GOG_KEYRING_PASSWORD='" + fixture_value + "'\n", encoding="utf-8")
    unsafe.chmod(0o644)
    consumers = (
        "scripts/health/apple_health_drive_sync.py",
        "scripts/health/health_pipeline.py",
        "scripts/health/generate_doctor_report.py",
        "scripts/cron/morning_briefing.py",
        "scripts/cron/weekly_health_drive_backup.py",
        "scripts/cron/health_daily_sync.py",
    )
    for index, relative in enumerate(consumers):
        mod = load_module(f"gog_permission_check_{index}", relative)
        monkeypatch.setattr(mod, "GOG_SECRET_ENV", unsafe)
        with pytest.raises(RuntimeError, match="Unsafe permissions"):
            mod.load_gog_keyring_password()


def test_doctor_report_has_no_automatic_medical_risk_scoring():
    source = (ROOT / "scripts/health/generate_doctor_report.py").read_text(encoding="utf-8")
    assert "score_from_threshold" not in source
    assert "Health Index" not in source
    assert "100=optimal" not in source
    assert 'lower(COALESCE(validierungsstatus,\'\')) = \'validiert\'' in source
    assert "verified_against_original = 1" in source
    assert "reference_range_source" in source
    assert "parse_reference_boundary" in source
    assert "NULLIF(TRIM(COALESCE(reference_min,'')), '') IS NOT NULL" in source
    assert "keine automatische medizinische Risiko-, Ampel- oder Entwarnungsbewertung" in source

    mod = load_module("doctor_report_reference_test", "scripts/health/generate_doctor_report.py")
    assert mod.parse_reference_boundary(None) is None
    assert mod.parse_reference_boundary("") is None
    assert mod.parse_reference_boundary("not-a-number") is None
    assert mod.parse_reference_boundary("5 mg/L") is None
    assert mod.parse_reference_boundary(" 5,25 ") == 5.25
    assert mod.parse_reference_boundary("-0.4") == -0.4


def test_dashboard_warning_copy_never_claims_medical_all_clear():
    source = (ROOT / "scripts/health/health_dashboard_v3.py").read_text(encoding="utf-8")
    assert "Keine erkannten Hinweise schließen medizinische Risiken nicht aus" in source
    assert "CRP über Referenz" not in source
    assert "D-Dimer über Referenz" not in source


def test_lab_warning_requires_validated_original_and_reference_range():
    mod = load_module("health_dashboard_test", "scripts/health/health_dashboard_v3.py")
    base = {
        "parameter_name": "Testparameter",
        "wert": "6.0",
        "einheit": "mg/l",
        "reference_min": "0",
        "reference_max": "5",
        "validierungsstatus": "validiert",
        "verified_against_original": 1,
        "reference_range_source": "scanned_original",
        "abnahme_datum": "2026-01-01",
        "befund_datum": None,
    }
    assert mod.evaluate_lab_warning(base) is not None
    assert mod.evaluate_lab_warning({**base, "validierungsstatus": "unvalidiert"}) is None
    assert mod.evaluate_lab_warning({**base, "verified_against_original": 0}) is None
    assert mod.evaluate_lab_warning({**base, "reference_range_source": "unknown"}) is None
    assert mod.evaluate_lab_warning({**base, "einheit": None}) is None
    assert mod.evaluate_lab_warning({**base, "reference_min": None, "reference_max": None}) is None
    assert mod.evaluate_lab_warning({**base, "reference_min": None, "reference_max": "5"}) is not None
    assert mod.evaluate_lab_warning({**base, "reference_min": "0", "reference_max": None}) is None


def test_lab_comparison_operator_is_not_stripped_into_false_alert():
    mod = load_module("health_dashboard_operator_test", "scripts/health/health_dashboard_v3.py")
    row = {
        "parameter_name": "Testparameter",
        "wert": "< 6",
        "einheit": "mg/l",
        "reference_min": "0",
        "reference_max": "5",
        "validierungsstatus": "validiert",
        "verified_against_original": 1,
        "reference_range_source": "scanned_original",
        "abnahme_datum": "2026-01-01",
        "befund_datum": None,
    }
    assert mod.evaluate_lab_warning(row) is None
    assert mod.evaluate_lab_warning({**row, "wert": "> 6"}) is not None
    assert mod.evaluate_lab_warning({**row, "wert": ">= 5"}) is None
    assert mod.evaluate_lab_warning({**row, "wert": ">= 6"}) is not None
    assert mod.evaluate_lab_warning({**row, "wert": "<= 0", "reference_min": "0"}) is None
    assert mod.evaluate_lab_warning({**row, "wert": "<= -1", "reference_min": "0"}) is not None
