from __future__ import annotations

import importlib.util
import json
import stat
from copy import deepcopy
from datetime import date
from pathlib import Path

import pytest


MODULE = "checkin_cli.nutrition_restriction_kb"


def _load():
    if importlib.util.find_spec(MODULE) is None:
        pytest.fail("nutrition restriction knowledge contract missing")
    return __import__(MODULE, fromlist=["*"])


def _template(mod) -> dict[str, object]:
    return mod.load_restriction_kb_template()


def test_restriction_kb_contract_and_unapproved_template_exist() -> None:
    mod = _load()
    template = _template(mod)
    assert template["schema_version"] == "nutrition_restriction_kb_v1"
    assert template["approved"] is False
    assert template["sources"]
    assert template["rules"]


def test_every_rule_cites_an_existing_source() -> None:
    mod = _load()
    validated = mod.validate_restriction_kb_template(
        _template(mod),
        as_of=date(2026, 8, 1),
    )
    source_ids = {source.source_id for source in validated.sources}
    assert all(rule.source_ids for rule in validated.rules)
    assert all(set(rule.source_ids) <= source_ids for rule in validated.rules)


def test_medication_condition_and_safety_rules_only_require_human_review() -> None:
    mod = _load()
    template = _template(mod)
    for rule in template["rules"]:
        if rule["category"] in {
            "medication",
            "condition",
            "pregnancy",
            "eating_disorder_risk",
        }:
            assert rule["action"] == "require_human_review"

    invalid = deepcopy(template)
    invalid["rules"].append(
        {
            "rule_id": "unsafe-prescription",
            "category": "medication",
            "terms": ["example"],
            "action": "exclude_food",
            "source_ids": ["nhs-food-intolerance"],
        }
    )
    with pytest.raises(ValueError, match="human review"):
        mod.validate_restriction_kb_template(invalid, as_of=date(2026, 8, 1))


def test_unknown_terms_remain_unresolved() -> None:
    mod = _load()
    validated = mod.validate_restriction_kb_template(
        _template(mod),
        as_of=date(2026, 8, 1),
    )
    result = mod.reconcile_restriction_terms(
        validated,
        {
            "allergies": ["unlisted-allergen"],
            "intolerances": [],
            "religious_ethical_exclusions": [],
            "conditions": [],
            "medications": [],
        },
    )
    assert result.resolved == ()
    assert result.unresolved == ("unlisted-allergen",)
    assert result.requires_human_review is True


def test_template_expiry_cannot_exceed_one_year() -> None:
    mod = _load()
    invalid = deepcopy(_template(mod))
    invalid["effective_date"] = "2026-08-01"
    invalid["expires_on"] = "2027-08-02"
    with pytest.raises(ValueError, match="one year"):
        mod.validate_restriction_kb_template(invalid, as_of=date(2026, 8, 1))


def test_customer_data_fields_are_rejected_from_global_kb() -> None:
    mod = _load()
    invalid = deepcopy(_template(mod))
    invalid["customer_key"] = "client_001"
    with pytest.raises(ValueError, match="customer"):
        mod.validate_restriction_kb_template(invalid, as_of=date(2026, 8, 1))


def test_seed_dry_run_does_not_write_and_commit_is_private(tmp_path: Path) -> None:
    mod = _load()
    source = tmp_path / "template.json"
    source.write_text(json.dumps(_template(mod)), encoding="utf-8")

    dry = mod.seed_restriction_kb(
        profile_root=tmp_path / "profile",
        source=source,
        owner_digest="a" * 64,
        commit=False,
        as_of=date(2026, 8, 1),
    )
    assert dry.committed is False
    assert not dry.runtime_path.exists()

    committed = mod.seed_restriction_kb(
        profile_root=tmp_path / "profile",
        source=source,
        owner_digest="a" * 64,
        commit=True,
        as_of=date(2026, 8, 1),
    )
    assert committed.committed is True
    assert stat.S_IMODE(committed.runtime_path.parent.stat().st_mode) == 0o700
    assert stat.S_IMODE(committed.runtime_path.stat().st_mode) == 0o600
    payload = json.loads(committed.runtime_path.read_text(encoding="utf-8"))
    assert payload["approved"] is True
    assert payload["owner_digest"] == "a" * 64
    assert len(payload["digest"]) == 64
