#!/usr/bin/env python3
"""Trusted latest-assistant-message observer boundary for receipt promotion.

Production adapters deliver finalized assistant content over the daemon-owned
observer socket after the daemon authenticates their peer against the broker's
kernel-anchored session identity. The broker request protocol never accepts
assistant-message content. Claude supplies its latest assistant entry; Pi
supplies finalized assistant content at ``message_end``.
"""

from __future__ import annotations

import json
import os
import stat
from pathlib import Path
from typing import Protocol


class ReceiptObserver(Protocol):
    def observe_latest_assistant_message(
        self,
        session_id: str,
        runtime: str,
        runtime_generation: int,
        binding: dict[str, object],
    ) -> str | None: ...


class UnavailableReceiptObserver:
    """Fail-closed only for direct unit construction without daemon transport."""

    def observe_latest_assistant_message(
        self,
        _session_id: str,
        _runtime: str,
        _runtime_generation: int,
        _binding: dict[str, object],
    ) -> str | None:
        return None


class RuntimeReceiptObserver:
    """Daemon-owned production observer populated only by authenticated adapters."""

    def __init__(self) -> None:
        self._messages: dict[tuple[str, str, int], str] = {}

    def record_latest_assistant_message(
        self,
        session_id: str,
        runtime: str,
        runtime_generation: int,
        message: str,
    ) -> None:
        self._messages[(session_id, runtime, runtime_generation)] = message

    def observe_latest_assistant_message(
        self,
        session_id: str,
        runtime: str,
        runtime_generation: int,
        _binding: dict[str, object],
    ) -> str | None:
        return self._messages.get((session_id, runtime, runtime_generation))


class TestReceiptObserver:
    """Deterministic controlled observer used only by byte-build tests."""

    def __init__(self) -> None:
        self._messages: dict[tuple[str, int], str] = {}

    def record_latest_assistant_message(
        self, session_id: str, runtime_generation: int, message: str
    ) -> None:
        self._messages[(session_id, runtime_generation)] = message

    def observe_latest_assistant_message(
        self,
        session_id: str,
        _runtime: str,
        runtime_generation: int,
        _binding: dict[str, object],
    ) -> str | None:
        return self._messages.get((session_id, runtime_generation))


class FileTestReceiptObserver:
    """Private fixture-file observer for isolated out-of-process test drivers only."""

    def __init__(self, path: Path) -> None:
        self.path = path

    def observe_latest_assistant_message(
        self,
        session_id: str,
        _runtime: str,
        runtime_generation: int,
        _binding: dict[str, object],
    ) -> str | None:
        flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0)
        try:
            descriptor = os.open(self.path, flags)
        except OSError:
            return None
        try:
            metadata = os.fstat(descriptor)
            if (
                not stat.S_ISREG(metadata.st_mode)
                or stat.S_IMODE(metadata.st_mode) != 0o600
                or metadata.st_uid != os.geteuid()
                or metadata.st_size > 64 * 1024
            ):
                return None
            raw = os.read(descriptor, 64 * 1024 + 1)
        finally:
            os.close(descriptor)
        if len(raw) > 64 * 1024:
            return None
        try:
            value = json.loads(raw)
        except (json.JSONDecodeError, UnicodeDecodeError):
            return None
        if (
            not isinstance(value, dict)
            or set(value) != {"session_id", "runtime_generation", "latest_assistant_message"}
            or value["session_id"] != session_id
            or value["runtime_generation"] != runtime_generation
            or not isinstance(value["latest_assistant_message"], str)
        ):
            return None
        return value["latest_assistant_message"]
