"""Hash-linked same-invocation evidence writer for the Task26 Golden Path.

This module records raw inputs and native API outcomes.  It deliberately emits no
PASS counters or verdicts; the independent verifier owns every acceptance rule.
"""
from __future__ import annotations

import hashlib
import json
import os
import secrets
from pathlib import Path
from typing import Mapping

ZERO = "0" * 64
CLAUSES = (
    "onboarding_22_clear",
    "deterministic_ambiguity_revision",
    "preview_isolation",
    "approved_card_projection_gate",
    "unknown_delivery_no_retry",
    "successful_lifecycle",
    "cleanup_resume_terminal",
)


def canonical(value: object) -> bytes:
    return json.dumps(
        value,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
        allow_nan=False,
    ).encode("utf-8")


def sha256_bytes(value: bytes) -> str:
    return hashlib.sha256(value).hexdigest()


class Task26EvidenceWriter:
    """Append one immutable artifact and one chained receipt per required clause."""

    def __init__(self, profile: Path, *, candidate_digest: str) -> None:
        self.root = profile / "task26-evidence"
        self.root.mkdir(mode=0o700)
        self.root.chmod(0o700)
        self.receipts = self.root / "receipts.jsonl"
        self.receipts.touch(mode=0o600)
        self.receipts.chmod(0o600)
        self.invocation_id = secrets.token_hex(16)
        self.candidate_digest = candidate_digest
        self._previous = ZERO
        self._sequence = 0

    def record(self, clause: str, payload: Mapping[str, object]) -> None:
        if clause not in CLAUSES or self._sequence >= len(CLAUSES):
            raise ValueError("Task26 clause is invalid or duplicated")
        if clause != CLAUSES[self._sequence]:
            raise ValueError("Task26 clauses must be emitted in contract order")
        self._sequence += 1
        artifact_name = f"{self._sequence:02d}-{clause}.json"
        artifact = {
            "schema": "task26-native-artifact-v1",
            "invocation_id": self.invocation_id,
            "candidate_digest": self.candidate_digest,
            "clause": clause,
            "payload": dict(payload),
        }
        artifact_bytes = canonical(artifact) + b"\n"
        artifact_path = self.root / artifact_name
        fd = os.open(
            artifact_path,
            os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_CLOEXEC | os.O_NOFOLLOW,
            0o600,
        )
        try:
            os.write(fd, artifact_bytes)
            os.fsync(fd)
        finally:
            os.close(fd)
        receipt = {
            "schema": "task26-native-receipt-v1",
            "sequence": self._sequence,
            "clause": clause,
            "invocation_id": self.invocation_id,
            "candidate_digest": self.candidate_digest,
            "previous_sha256": self._previous,
            "artifact": artifact_name,
            "artifact_sha256": sha256_bytes(artifact_bytes),
        }
        receipt["receipt_sha256"] = sha256_bytes(canonical(receipt))
        with self.receipts.open("ab") as stream:
            stream.write(canonical(receipt) + b"\n")
            stream.flush()
            os.fsync(stream.fileno())
        self._previous = str(receipt["receipt_sha256"])

    def finish(self) -> str:
        if self._sequence != len(CLAUSES):
            raise RuntimeError("Task26 evidence contract is incomplete")
        return self._previous
