#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# dependencies = []
# ///

# ─── How to run ───
# 1. Install uv: curl -LsSf https://astral.sh/uv/install.sh | sh
# 2. Use: uv run python scripts/run_nutricoach_v140_golden_path.py --verify --help
# ──────────────────

"""Completed-artifact manifest builder and fail-closed verifier."""

from __future__ import annotations

import hashlib
import json
from dataclasses import dataclass
from pathlib import Path
from typing import ClassVar, override

from pydantic import BaseModel, ConfigDict, Field, ValidationError

from checkin_cli.weekly_operations import WeeklyOperationsError
from gateway.platforms.nutrition_weekly_owner_model import JsonValue
from gateway.platforms.nutrition_weekly_reminder_bootstrap_config import (
    WeeklyReminderStartupAuthorityIncident,
)
from scripts.nutricoach_v140_golden_path_models import ManifestMember
from scripts.nutricoach_v140_golden_path_receipt import build_receipt
from scripts.nutricoach_v140_golden_path_transport import read_provider_calls


class SchemaProbe(BaseModel):
    model_config: ClassVar[ConfigDict] = ConfigDict(extra="allow")

    schema_value: str | None = Field(default=None, alias="schema")
    schema_version: str | None = None
    kind: str | None = None


class ManifestDocument(BaseModel):
    model_config: ClassVar[ConfigDict] = ConfigDict(frozen=True, extra="forbid")

    schema_value: str = Field(alias="schema")
    receipt: ManifestMember
    members: list[ManifestMember]


@dataclass(frozen=True, slots=True)
class ArtifactMismatch(Exception):
    member: str
    reason: str

    @override
    def __str__(self) -> str:
        return f"artifact mismatch: {self.member}: {self.reason}"


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


def _schema(path: Path) -> str:
    if path.stat().st_size == 0:
        return "empty"
    try:
        first = path.read_text(encoding="utf-8").splitlines()[0]
        raw = SchemaProbe.model_validate_json(first)
    except (IndexError, OSError, UnicodeDecodeError, ValidationError, json.JSONDecodeError):
        return "opaque"
    return raw.schema_value or raw.schema_version or raw.kind or "json"


def _members(root: Path) -> tuple[ManifestMember, ...]:
    return tuple(
        ManifestMember(
            path=path.relative_to(root).as_posix(), schema=_schema(path),
            sha256=_sha256(path), size=path.stat().st_size,
        )
        for path in sorted(root.rglob("*"))
        if path.is_file() and not path.is_symlink()
    )


def build_manifest(root: Path, receipt: Path) -> dict[str, JsonValue]:
    receipt_member: dict[str, JsonValue] = {
        "path": receipt.name, "schema": "nutricoach-v140-task-10-golden-path-r3",
        "sha256": _sha256(receipt), "size": receipt.stat().st_size,
    }
    members: list[JsonValue] = [
        {
            "path": member.path, "schema": member.schema_name,
            "sha256": member.sha256, "size": member.size,
        }
        for member in _members(root)
    ]
    return {
        "schema": "nutricoach-v140-task-10-manifest-r3",
        "receipt": receipt_member, "members": members,
    }


def verify_artifacts(root: Path, receipt_path: Path, manifest_path: Path) -> None:
    """Recompute receipt facts and every declared member hash."""
    try:
        supplied_receipt = receipt_path.read_bytes()
        supplied_manifest = ManifestDocument.model_validate_json(
            manifest_path.read_text(encoding="utf-8")
        )
    except (OSError, ValueError, ValidationError, json.JSONDecodeError) as error:
        raise ArtifactMismatch("document", "unreadable") from error
    calls = read_provider_calls(root / "provider-transcript.jsonl")
    if sum(len(call.privacy_leak_categories) for call in calls) != 0:
        raise ArtifactMismatch("privacy", "rendered provider text leaked a sentinel")
    try:
        expected_receipt = build_receipt(root)
    except (
        OSError, ValueError, WeeklyOperationsError, WeeklyReminderStartupAuthorityIncident,
    ) as error:
        raise ArtifactMismatch("authoritative-store", type(error).__name__) from error
    expected_bytes = (json.dumps(expected_receipt, sort_keys=True, separators=(",", ":")) + "\n").encode()
    if supplied_receipt != expected_bytes:
        raise ArtifactMismatch("receipt", "recomputed facts disagree")
    expected_manifest = ManifestDocument.model_validate(build_manifest(root, receipt_path))
    if supplied_manifest != expected_manifest:
        supplied_members = supplied_manifest.members
        expected_members = expected_manifest.members
        if supplied_members != expected_members:
            raise ArtifactMismatch("manifest member", "path/hash/schema inventory disagrees")
        raise ArtifactMismatch("receipt digest", "manifest receipt binding disagrees")
