"""Hash manifest and semantic verifier for Todo11 artifacts."""

from __future__ import annotations

import hashlib
from collections.abc import Sequence
from pathlib import Path

from pydantic import JsonValue as PydanticJsonValue, TypeAdapter

from scripts.nutricoach_v140_runtime import JsonValue, write_json


MANIFEST = "task-11-manifest-r3.json"
RECEIPT = "task-11-telegram-e2e-r3.json"
TRANSCRIPT = "task-11-transcript-r3.json"
CLEANUP = "task-11-cleanup-r3.json"


class ArtifactContractError(ValueError):
    """An artifact is outside the finite Todo11 JSON contract."""


def artifact_sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def write_artifact_manifest(root: Path, names: Sequence[str]) -> None:
    artifacts: dict[str, JsonValue] = {
        name: artifact_sha256(root / name) for name in names
    }
    write_json(root / MANIFEST, {
        "schema": "nutricoach-v140-task11-manifest-v3",
        "status": "PASS",
        "artifacts_sha256": artifacts,
    })


def _load(path: Path) -> JsonValue:
    adapter: TypeAdapter[PydanticJsonValue] = TypeAdapter(PydanticJsonValue)
    return adapter.validate_json(path.read_text(encoding="utf-8"))


def _object(value: JsonValue, label: str) -> dict[str, JsonValue]:
    if not isinstance(value, dict):
        raise ArtifactContractError(label)
    return value


def _string(value: JsonValue, label: str) -> str:
    if not isinstance(value, str):
        raise ArtifactContractError(label)
    return value


def verify_artifacts(root: Path) -> str | None:
    """Return one stable reason, or None when hashes and semantics pass."""
    try:
        manifest = _object(_load(root / MANIFEST), "manifest_shape")
        hashes = _object(manifest.get("artifacts_sha256"), "manifest_hashes")
        for name, digest in hashes.items():
            expected = _string(digest, "manifest_digest")
            path = root / name
            if not path.is_file() or artifact_sha256(path) != expected:
                return f"HASH_MISMATCH:{name}"
        receipt = _object(_load(root / RECEIPT), "receipt_shape")
        transcript_value = _load(root / TRANSCRIPT)
        cleanup = _object(_load(root / CLEANUP), "cleanup_shape")
    except (ArtifactContractError, OSError, ValueError) as error:
        return f"CONTRACT_INVALID:{error}"
    if (
        receipt.get("status") != "PASS"
        or receipt.get("privacy_leaks") != 0
        or receipt.get("network_calls") != 0
        or cleanup.get("temporary_profile_removed") is not True
    ):
        return "RECEIPT_INVARIANT"
    topic = receipt.get("topic59")
    if not isinstance(topic, dict):
        return "TOPIC59_RECEIPT"
    message_id = topic.get("message_id")
    if (
        not isinstance(message_id, str)
        or message_id != topic.get("edited_message_id")
    ):
        return "TOPIC59_MESSAGE_ID"
    if not isinstance(transcript_value, list):
        return "TRANSCRIPT_SHAPE"
    sent = False
    edited = False
    for item in transcript_value:
        if not isinstance(item, dict):
            return "TRANSCRIPT_ROW"
        if item.get("raw_sentinels_absent") is not True:
            return "TRANSCRIPT_PRIVACY"
        matches = (
            item.get("chat_id") == "200"
            and item.get("topic_id") == "59"
            and item.get("message_id") == message_id
        )
        sent = sent or (matches and item.get("method") == "sendMessage")
        edited = edited or (matches and item.get("method") == "editMessageText")
    return None if sent and edited else "TRANSCRIPT_TOPIC59_BINDING"