"""Exact Task10 evidence boundary for r71b candidate sealing and replay."""

from __future__ import annotations

import hashlib
import json
import os
import stat
from dataclasses import dataclass
from pathlib import Path
from typing import Final, Literal, final

from pydantic import JsonValue, TypeAdapter, ValidationError

from scripts.nutricoach_v150_sealed_authority import atomic_write
from scripts.verify_nutricoach_v140_candidate_core import canonical

_PLAN_EVIDENCE_DIRECTORY: Final = Path(
    "/home/cube/projects/richard/traning coach/.omo/evidence/nutricoach-telegram-checkin-stepper"
)
_COPY_SCHEMA: Final = "nutricoach-v150-r71b-task10-evidence-copy-v1"
_OBJECT = TypeAdapter(dict[str, JsonValue])
_JSON_LIST = TypeAdapter(list[JsonValue])


@final
@dataclass(frozen=True, slots=True)
class Task10EvidenceSpec:
    """One immutable Task10 source document admitted to r71b derivation."""

    input_name: str
    source_name: str
    source_sha256: str
    source_schema: str
    task_id: str
    status_field: Literal["status", "verdict"]
    status: str

    @property
    def source_path(self) -> Path:
        return _PLAN_EVIDENCE_DIRECTORY / self.source_name


R71B_TASK10_EVIDENCE: Final = (
    Task10EvidenceSpec(
        "r70_error",
        "task-10-cron-no-send-recovery-analysis.json",
        "968a3ea4e15d149736d47c239d410ed9b88b7dbb20e0703214a2a6b19c3025d0",
        "nutricoach-telegram-checkin-stepper-task-10-cron-no-send-recovery-analysis-v1",
        "st_01a05b16",
        "verdict",
        "NO_SAFE_PATH",
    ),
    Task10EvidenceSpec(
        "observer",
        "task-10-debug-observer-r70.json",
        "5f367a44f23dfa072ea1a1bb4fbdb9e216e8c832fbcc608157ad0624de4541a9",
        "nutricoach-telegram-checkin-stepper-task-10-debug-observer-r70-v1",
        "st_01a05b00",
        "verdict",
        "CONFIRMED_OBSERVER_INPUT_ROOT_BUG_WITH_SEPARATE_UNRESOLVED_CRON_RUNTIME_DEFECT",
    ),
    Task10EvidenceSpec(
        "health_recovery",
        "task-10-r70-health-recovery.json",
        "b1bbe2fec619d66570b5025d7760878ae3f0421825c50b19630065fbf8963f13",
        "nutricoach-telegram-checkin-stepper-task-10-r70-health-recovery-v1",
        "st_01a05b07",
        "status",
        "BLOCKED_RESTART_SAFETY_NO_REPLAY",
    ),
)


class Task10EvidenceError(RuntimeError):
    """A Task10 evidence source or its candidate-bound copy is not authentic."""


@final
@dataclass(frozen=True, slots=True)
class ValidatedTask10Evidence:
    """Validated immutable evidence retained only for one sealing/replay pass."""

    spec: Task10EvidenceSpec
    document: dict[str, JsonValue]


@final
@dataclass(frozen=True, slots=True)
class CandidateTask10EvidenceCopy:
    """Canonical candidate copy preserving source provenance and parsed evidence."""

    input_name: str
    source_path: str
    source_sha256: str
    source_schema: str
    source_status: str
    document: dict[str, JsonValue]


def task10_evidence_spec(name: str) -> Task10EvidenceSpec:
    """Return one fixed source specification by its stable logical name."""
    matches = [spec for spec in R71B_TASK10_EVIDENCE if spec.input_name == name]
    if len(matches) != 1:
        raise Task10EvidenceError("task10_evidence_name")
    return matches[0]


def validate_task10_evidence_source(
    path: Path,
    spec: Task10EvidenceSpec,
) -> ValidatedTask10Evidence:
    """Read one exact source through no-follow ownership and semantic fences."""
    if not path.is_absolute() or path != spec.source_path:
        raise Task10EvidenceError("task10_evidence_path")
    _require_evidence_directory(spec.source_path.parent)
    payload = _read_exact_regular(path, 0o664, "task10_evidence_file")
    if hashlib.sha256(payload).hexdigest() != spec.source_sha256:
        raise Task10EvidenceError("task10_evidence_sha256")
    document = _parse_object(payload, "task10_evidence_json")
    if (
        document.get("schema") != spec.source_schema
        or document.get("task_id") != spec.task_id
        or document.get(spec.status_field) != spec.status
    ):
        raise Task10EvidenceError("task10_evidence_identity")
    _require_semantics(spec, document)
    return ValidatedTask10Evidence(spec, document)


def validate_fixed_task10_evidence_sources(
    r70_error: Path,
    observer: Path,
    health_recovery: Path,
) -> tuple[ValidatedTask10Evidence, ...]:
    """Accept exactly the three resolved Task10 plan evidence source paths."""
    requested = (r70_error, observer, health_recovery)
    if len(requested) != len(R71B_TASK10_EVIDENCE):
        raise Task10EvidenceError("task10_evidence_count")
    return tuple(
        validate_task10_evidence_source(path, spec)
        for path, spec in zip(requested, R71B_TASK10_EVIDENCE, strict=True)
    )


def write_candidate_task10_evidence(
    destination: Path,
    evidence: ValidatedTask10Evidence,
) -> None:
    """Write one canonical, provenance-preserving candidate input document."""
    spec = evidence.spec
    payload: dict[str, JsonValue] = {
        "schema": _COPY_SCHEMA,
        "input_name": spec.input_name,
        "source_path": str(spec.source_path),
        "source_sha256": spec.source_sha256,
        "source_schema": spec.source_schema,
        "source_status": spec.status,
        "source_document": evidence.document,
    }
    atomic_write(destination, canonical(payload) + b"\n", 0o644)


def load_candidate_task10_evidence(
    candidate_root: Path,
    relative: str,
    spec: Task10EvidenceSpec,
) -> CandidateTask10EvidenceCopy:
    """Verify one immutable candidate copy against its original exact source."""
    path = candidate_root / relative
    payload = _read_exact_regular(path, 0o444, "candidate_task10_evidence_file")
    document = _parse_object(payload, "candidate_task10_evidence_json")
    if canonical(document) + b"\n" != payload:
        raise Task10EvidenceError("candidate_task10_evidence_canonical")
    if (
        document.get("schema") != _COPY_SCHEMA
        or document.get("input_name") != spec.input_name
        or document.get("source_path") != str(spec.source_path)
        or document.get("source_sha256") != spec.source_sha256
        or document.get("source_schema") != spec.source_schema
        or document.get("source_status") != spec.status
    ):
        raise Task10EvidenceError("candidate_task10_evidence_provenance")
    source_document = document.get("source_document")
    if not isinstance(source_document, dict):
        raise Task10EvidenceError("candidate_task10_evidence_document")
    source = validate_task10_evidence_source(spec.source_path, spec)
    if source.document != source_document:
        raise Task10EvidenceError("candidate_task10_evidence_substitution")
    return CandidateTask10EvidenceCopy(
        spec.input_name,
        str(spec.source_path),
        spec.source_sha256,
        spec.source_schema,
        spec.status,
        source.document,
    )


def verify_candidate_task10_evidence(
    candidate_root: Path,
    paths: dict[str, str],
) -> tuple[CandidateTask10EvidenceCopy, ...]:
    """Reconstruct all Task10 provenance from candidate files and fixed sources."""
    if set(paths) != {spec.input_name for spec in R71B_TASK10_EVIDENCE}:
        raise Task10EvidenceError("candidate_task10_evidence_paths")
    return tuple(
        load_candidate_task10_evidence(candidate_root, paths[spec.input_name], spec)
        for spec in R71B_TASK10_EVIDENCE
    )


def candidate_task10_provenance(
    copies: tuple[CandidateTask10EvidenceCopy, ...],
) -> list[JsonValue]:
    """Return the exact provenance rows retained in preseal derivation inputs."""
    return _JSON_LIST.validate_python([
        {
            "input_name": copy.input_name,
            "source_path": copy.source_path,
            "source_sha256": copy.source_sha256,
            "source_schema": copy.source_schema,
            "source_status": copy.source_status,
        }
        for copy in copies
    ])


def _require_evidence_directory(path: Path) -> None:
    info = path.stat(follow_symlinks=False)
    if (
        path != _PLAN_EVIDENCE_DIRECTORY
        or path.is_symlink()
        or not stat.S_ISDIR(info.st_mode)
        or stat.S_IMODE(info.st_mode) != 0o775
        or info.st_uid != os.geteuid()
        or info.st_gid != os.getegid()
    ):
        raise Task10EvidenceError("task10_evidence_parent")


def _read_exact_regular(path: Path, mode: int, label: str) -> bytes:
    try:
        before = path.stat(follow_symlinks=False)
        if (
            path.is_symlink()
            or not stat.S_ISREG(before.st_mode)
            or before.st_nlink != 1
            or stat.S_IMODE(before.st_mode) != mode
            or before.st_uid != os.geteuid()
            or before.st_gid != os.getegid()
        ):
            raise Task10EvidenceError(label)
        descriptor = os.open(path, os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW)
    except OSError as error:
        raise Task10EvidenceError(label) from error
    try:
        opened = os.fstat(descriptor)
        if (
            opened.st_dev != before.st_dev
            or opened.st_ino != before.st_ino
            or not stat.S_ISREG(opened.st_mode)
            or opened.st_nlink != 1
            or stat.S_IMODE(opened.st_mode) != mode
            or opened.st_uid != os.geteuid()
            or opened.st_gid != os.getegid()
        ):
            raise Task10EvidenceError(label)
        payload = _read_descriptor(descriptor)
    finally:
        os.close(descriptor)
    try:
        after = path.stat(follow_symlinks=False)
    except OSError as error:
        raise Task10EvidenceError(label) from error
    if (after.st_dev, after.st_ino) != (before.st_dev, before.st_ino):
        raise Task10EvidenceError(label)
    return payload


def _read_descriptor(descriptor: int) -> bytes:
    chunks: list[bytes] = []
    while True:
        chunk = os.read(descriptor, 65_536)
        if not chunk:
            return b"".join(chunks)
        chunks.append(chunk)


def _parse_object(payload: bytes, label: str) -> dict[str, JsonValue]:
    try:
        value = _OBJECT.validate_json(payload)
    except ValidationError as error:
        raise Task10EvidenceError(label) from error
    try:
        json.loads(payload.decode("utf-8"), object_pairs_hook=_unique_object)
    except (UnicodeDecodeError, json.JSONDecodeError, Task10EvidenceError) as error:
        raise Task10EvidenceError(label) from error
    return value


def _unique_object(pairs: list[tuple[str, object]]) -> dict[str, object]:
    result: dict[str, object] = {}
    for key, value in pairs:
        if key in result:
            raise Task10EvidenceError("task10_evidence_duplicate_key")
        result[key] = value
    return result


def _require_semantics(
    spec: Task10EvidenceSpec,
    document: dict[str, JsonValue],
) -> None:
    match spec.input_name:
        case "r70_error":
            _require_cron_evidence(document)
        case "observer":
            _require_observer_evidence(document)
        case "health_recovery":
            _require_health_recovery_evidence(document)
        case _:
            raise Task10EvidenceError("task10_evidence_name")


def _mapping(value: JsonValue | None, label: str) -> dict[str, JsonValue]:
    if not isinstance(value, dict):
        raise Task10EvidenceError(label)
    return value


def _require_zeroes(document: dict[str, JsonValue], label: str, keys: tuple[str, ...]) -> None:
    values = _mapping(document.get(label), label)
    if any(values.get(key) != 0 for key in keys):
        raise Task10EvidenceError(label)


def _require_cron_evidence(document: dict[str, JsonValue]) -> None:
    oracle = _mapping(
        _mapping(document.get("observed_state_basis"), "cron_observed").get(
            "next_tick_oracle"
        ),
        "cron_oracle",
    )
    if (
        oracle.get("safe_to_restart") is not False
        or oracle.get("due_send_count_per_independent_future_tick") != 1
        or oracle.get("due_update_count_per_independent_future_tick") != 0
    ):
        raise Task10EvidenceError("cron_oracle")
    _require_zeroes(
        document,
        "forbidden_effects",
        (
            "source_edits",
            "live_config_or_ledger_mutations",
            "cron_job_mutations",
            "cron_ticks_or_jobs_executed",
            "service_restarts",
            "observer_appends",
            "network_or_provider_actions",
            "telegram_sends",
            "telegram_updates",
            "customer_or_authority_actions",
            "candidate_or_preseal_mutations",
        ),
    )
    if _mapping(document.get("forbidden_effects"), "forbidden_effects").get(
        "customer_values_exposed"
    ) is not False:
        raise Task10EvidenceError("forbidden_effects")


def _require_observer_evidence(document: dict[str, JsonValue]) -> None:
    protected = _mapping(document.get("protected_state"), "observer_protected")
    if (
        protected.get("launcher_invocation_count") != 0
        or protected.get("launcher_process_count") != 0
        or any(
            protected.get(key) != "ABSENT"
            for key in (
                "r71_authorization_root",
                "r71_execution_root",
                "r71_successor_runtime_root",
                "observer_r71_root",
            )
        )
    ):
        raise Task10EvidenceError("observer_protected")
    draft = _mapping(protected.get("live_draft"), "observer_draft")
    plan = _mapping(document.get("safe_recovery_plan"), "observer_plan")
    if (
        draft.get("customer_values_recorded") is not False
        or plan.get("customer_input_required") is not False
        or plan.get("canonical_observer_pass_possible") is not True
    ):
        raise Task10EvidenceError("observer_customer")
    _require_zeroes(
        document,
        "no_effects",
        (
            "git_or_github_actions",
            "source_or_live_state_edits",
            "observer_rows_appended_by_task",
            "service_or_timer_restarts",
            "network_or_customer_messages",
            "customer_updates",
            "authority_actions",
            "launcher_invocations",
        ),
    )


def _require_health_recovery_evidence(document: dict[str, JsonValue]) -> None:
    phase_b = _mapping(document.get("phase_b_restart_safety"), "health_phase_b")
    if (
        phase_b.get("provider_sends") != 0
        or phase_b.get("provider_updates") != 0
        or phase_b.get("network_attempts") != 0
        or phase_b.get("restart_gate") != "DENIED"
    ):
        raise Task10EvidenceError("health_phase_b")
    live = _mapping(document.get("live_state_after_stop"), "health_live")
    if (
        live.get("r71_launcher_process_count") != 0
        or live.get("r71_launcher_invocation_count") != 0
        or any(
            live.get(key) is not True
            for key in (
                "r71_authorization_root_absent",
                "r71_execution_root_absent",
                "r71_successor_runtime_root_absent",
                "r71_observer_root_absent",
            )
        )
    ):
        raise Task10EvidenceError("health_roots")
    _require_zeroes(
        document,
        "forbidden_effects",
        (
            "git_or_github_actions",
            "candidate_or_preseal_mutations",
            "service_restarts",
            "cron_job_mutations",
            "observer_invocations_by_task",
            "r71_launcher_invocations",
            "customer_messages",
            "customer_updates",
            "network_effects",
            "customer_data_fabrication",
            "commits",
        ),
    )
