from __future__ import annotations

import hashlib
import importlib.util
import json
import os
import shutil
from datetime import datetime
from pathlib import Path
from typing import Any

import pytest

HERE = Path(__file__).resolve().parent
SPEC = importlib.util.spec_from_file_location("post_claim_adjudicator_v2", HERE / "post_claim_adjudicator_v2.py")
assert SPEC is not None and SPEC.loader is not None
adjudicator = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(adjudicator)
LIVE = Path("/home/cube/.hermes/profiles/dualcoachtest")
EVIDENCE = HERE.parent


def tree_hash(root: Path) -> str:
    digest = hashlib.sha256()
    for path in sorted(item for item in root.rglob("*") if item.is_file()):
        digest.update(str(path.relative_to(root)).encode())
        digest.update(path.read_bytes())
    return digest.hexdigest()


def fixture(tmp_path: Path) -> Path:
    profile = tmp_path / "profile"
    sources = {
        "data/onboarding/telegram-customer-bootstrap-v1/ledger.json": LIVE / "data/onboarding/telegram-customer-bootstrap-v1/ledger.json",
        "customers/registry.json": LIVE / "customers/registry.json",
        "workspace/checkin_cli/checkin_cli/customer_admin.py": LIVE / "workspace/checkin_cli/checkin_cli/customer_admin.py",
    }
    for relative, source in sources.items():
        target = profile / relative
        target.parent.mkdir(parents=True, mode=0o700, exist_ok=True)
        shutil.copyfile(source, target)
        target.chmod(0o600)
    ledger = profile / "data/onboarding/telegram-customer-bootstrap-v1/ledger.json"
    value = json.loads(ledger.read_text())
    created = datetime.fromisoformat(value["sessions"][1]["created_at"]).timestamp()
    updated = datetime.fromisoformat(value["sessions"][1]["updated_at"]).timestamp()
    os.utime(ledger, (updated, updated))
    registry = profile / "customers/registry.json"
    midpoint = created + ((updated - created) / 2)
    os.utime(registry, (midpoint, midpoint))
    return profile


def service_stub() -> dict[str, Any]:
    return {"service": "active/running", "candidate_modules": {}, "normal_path_transitions": []}


def test_exact_claim_and_disabled_registration_passes_read_only(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    profile = fixture(tmp_path)
    monkeypatch.setattr(adjudicator, "source_semantics_and_service", service_stub)
    before = tree_hash(profile)
    receipt = adjudicator.adjudicate(profile, EVIDENCE)
    assert receipt["status"] == "PASS_SUCCESSFUL_CLAIM_AND_DISABLED_REGISTRATION"
    assert receipt["accepted_role_claim_count"] == 1
    assert receipt["consent_publication_count"] == 1
    assert receipt["preparation_ledger_sha256"] == adjudicator.PREPARATION_LEDGER_SHA256
    assert receipt["current_payload_digest"] == adjudicator.CURRENT_LEDGER_DIGEST
    registration = receipt["disabled_registration_proof"]
    assert registration["customer_registry_rows"] == 1
    assert registration["customer_key"] == adjudicator.CUSTOMER_KEY
    assert registration["customer_user_id"] == adjudicator.ACTOR
    assert registration["status"] == "disabled/not_activated"
    assert registration["schedule_delivery_count"] == 0
    assert receipt["token_binding"]["raw_token_only_private_handoff"] is True
    assert tree_hash(profile) == before
    assert "start_token" not in json.dumps(receipt)


@pytest.mark.parametrize(
    ("attack", "message"),
    [
        ("zero", "exactly one canonical registry row"),
        ("two", "exactly one canonical registry row"),
        ("key", "identity/status"),
        ("enabled", "identity/status"),
        ("consent", "identity/status"),
        ("telegram", "identity/status"),
        ("schedule", "schedule configuration"),
        ("plan", "plan projection"),
    ],
)
def test_registry_mismatch_fails_closed(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch, attack: str, message: str
) -> None:
    profile = fixture(tmp_path)
    monkeypatch.setattr(adjudicator, "source_semantics_and_service", service_stub)
    registry_path = profile / "customers/registry.json"
    registry = json.loads(registry_path.read_text())
    row = registry["customers"][0]
    if attack == "zero":
        registry["customers"] = []
    elif attack == "two":
        registry["customers"].append(dict(row))
    elif attack == "key":
        row["customer_key"] = "wrong"
    elif attack == "enabled":
        row["enabled"] = True
    elif attack == "consent":
        row["ai_processing_consent"]["granted"] = True
    elif attack == "telegram":
        row["telegram"]["user_id"] = "1"
    elif attack == "schedule":
        row["schedule"]["daily_time"] = "09:00:00"
    else:
        row["plan"]["weeks"][0]["calories_kcal"] = 1
    registry_path.write_text(json.dumps(registry, sort_keys=True, separators=(",", ":")))
    with pytest.raises(adjudicator.AdjudicationError, match=message):
        ledger = json.loads((profile / "data/onboarding/telegram-customer-bootstrap-v1/ledger.json").read_text())
        _, replacement, draft = adjudicator.exact_sessions(ledger)
        adjudicator.disabled_registration(profile, replacement, draft)


def test_registry_provenance_must_be_between_prepare_and_final_transition(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    profile = fixture(tmp_path)
    monkeypatch.setattr(adjudicator, "source_semantics_and_service", service_stub)
    registry = profile / "customers/registry.json"
    os.utime(registry, (1, 1))
    with pytest.raises(adjudicator.AdjudicationError, match="provenance timestamp"):
        adjudicator.adjudicate(profile, EVIDENCE)


def test_downstream_activation_or_delivery_authority_fails(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    profile = fixture(tmp_path)
    monkeypatch.setattr(adjudicator, "source_semantics_and_service", service_stub)
    forbidden = profile / "data/scheduled-deliveries.jsonl"
    forbidden.parent.mkdir(exist_ok=True)
    forbidden.write_text("")
    with pytest.raises(adjudicator.AdjudicationError, match="activation/checkin/generation/delivery"):
        adjudicator.adjudicate(profile, EVIDENCE)


def test_claim_and_consent_card_cardinality_remain_exact(
    tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
    profile = fixture(tmp_path)
    monkeypatch.setattr(adjudicator, "source_semantics_and_service", service_stub)
    ledger = json.loads((profile / "data/onboarding/telegram-customer-bootstrap-v1/ledger.json").read_text())
    ledger["sessions"][1]["role_claims"].append(dict(ledger["sessions"][1]["role_claims"][0]))
    with pytest.raises(adjudicator.AdjudicationError, match="accepted role claim"):
        adjudicator.exact_sessions(ledger)
    ledger["sessions"][1]["role_claims"] = ledger["sessions"][1]["role_claims"][:1]
    ledger["sessions"][1]["consent_publication_attempt"] = 2
    with pytest.raises(adjudicator.AdjudicationError, match="replacement final state"):
        adjudicator.exact_sessions(ledger)


def test_receipt_is_append_only_one_shot(tmp_path: Path) -> None:
    receipt = tmp_path / "receipt.json"
    adjudicator.exclusive_receipt(receipt, {"status": "FAIL"})
    with pytest.raises(FileExistsError):
        adjudicator.exclusive_receipt(receipt, {"status": "PASS_SUCCESSFUL_CLAIM_AND_DISABLED_REGISTRATION"})
    assert json.loads(receipt.read_text()) == {"status": "FAIL"}
