"""Typed Telegram publication seam for durable check-in ingress.

This module deliberately keeps customer answers and callback data outside the
journal.  The Todo 4 stepper receives only typed identities and a transient
prompt publication.
"""

from __future__ import annotations

from collections.abc import Awaitable, Callable
from dataclasses import dataclass
from enum import StrEnum
from typing import Protocol, final

from gateway.platforms.physique_checkin_bindings import CursorIdentity, IngressIdentity
from gateway.platforms.telegram_physique_checkin_stepper import (
    DeliveryRejected,
    TransportPreflightError,
)


class TelegramCheckinIngressDisposition(StrEnum):
    """Whether typed check-in ingress may reach a legacy handler."""

    NOT_APPLICABLE = "not_applicable"
    RESERVED_INVALID = "reserved_invalid"
    RESERVED = "reserved"


@dataclass(frozen=True, slots=True)
class StepperDomainResult:
    """Transient outcome of the bridge's already-authorized domain CAS."""

    accepted: bool
    publication: object | None


@dataclass(frozen=True, slots=True)
class TelegramPromptPublication:
    """One in-memory prompt and its exact post-send binding operation."""

    text: str
    reply_markup: object | None
    bind_message_id: Callable[[int], None] | None = None


@dataclass(frozen=True, slots=True)
class PreparedTelegramPrompt:
    """Locally validated publication ready for exactly one provider call."""

    text: str
    reply_markup: object | None
    bind_message_id: Callable[[int], None] | None


@dataclass(frozen=True, slots=True)
class TelegramPromptReceipt:
    """The one provider receipt retained by the Todo 4 projection."""

    message_id: int


@dataclass(slots=True)
class TelegramCheckinTransition:
    """Typed ingress identity plus a caller-owned single domain mutation."""

    ingress: IngressIdentity
    source: CursorIdentity
    target: CursorIdentity
    expires_at: int
    commit_operation: Callable[[], StepperDomainResult]

    def commit(self) -> StepperDomainResult:
        return self.commit_operation()


class _PreparedPromptSender(Protocol):
    def __call__(self, prompt: PreparedTelegramPrompt) -> Awaitable[object]: ...


class _DefinitiveRejection(Protocol):
    def __call__(self, error: Exception) -> bool: ...


@final
class TelegramCheckinPromptTransport:
    """One strict Telegram send with no retry or topic fallback.

    Binding the sent message to the bridge is part of the provider operation:
    if that durable local write fails after Telegram accepted the request, the
    stepper records an uncertain result and never replays the send.
    """

    def __init__(
        self,
        sender: _PreparedPromptSender,
        *,
        is_definitive_rejection: _DefinitiveRejection,
    ) -> None:
        self._sender: _PreparedPromptSender = sender
        self._is_definitive_rejection: _DefinitiveRejection = is_definitive_rejection

    def prepare(self, publication: object) -> PreparedTelegramPrompt:
        if not isinstance(publication, TelegramPromptPublication):
            raise TransportPreflightError("invalid Telegram prompt publication")
        if not publication.text.strip() or len(publication.text) > 4_096:
            raise TransportPreflightError("invalid Telegram prompt text")
        return PreparedTelegramPrompt(
            publication.text,
            publication.reply_markup,
            publication.bind_message_id,
        )

    async def send(self, prepared: object) -> TelegramPromptReceipt:
        if not isinstance(prepared, PreparedTelegramPrompt):
            raise TransportPreflightError("invalid prepared Telegram prompt")
        try:
            response = await self._sender(prepared)
        except Exception as exc:
            if self._is_definitive_rejection(exc):
                raise DeliveryRejected("Telegram rejected check-in prompt") from exc
            raise
        message_id = getattr(response, "message_id", None)
        if type(message_id) is not int or message_id <= 0:
            raise RuntimeError("Telegram prompt receipt is invalid")
        if prepared.bind_message_id is not None:
            prepared.bind_message_id(message_id)
        return TelegramPromptReceipt(message_id)
