from __future__ import annotations

import hashlib
import threading
from dataclasses import dataclass, field
from typing import Protocol

from k1link.device_plugins.xgrids_k1.protocol.application_bootstrap import (
    EncodedApplicationRequest,
)
from k1link.device_plugins.xgrids_k1.protocol.modeling_safety import (
    ShadowModelingCommand,
)


class VendorWritesDisabledError(RuntimeError):
    """The active compatibility profile cannot touch a publish transport."""


class ApplicationPublishSink(Protocol):
    def publish(
        self,
        *,
        topic: str,
        payload: bytes,
        qos: int,
        retain: bool,
    ) -> None: ...


class UninstalledApplicationPublishSink:
    """Explicit placeholder proving that no live MQTT sink is installed."""

    def publish(
        self,
        *,
        topic: str,
        payload: bytes,
        qos: int,
        retain: bool,
    ) -> None:
        del topic, payload, qos, retain
        raise VendorWritesDisabledError("live application publish sink is not installed")


@dataclass(frozen=True, slots=True)
class OneShotPublishEnvelope:
    operation_key: str
    topic: str
    payload: bytes = field(repr=False)
    payload_sha256: str
    payload_bytes: int
    qos: int
    retain: bool
    automatic_retry: bool = False

    @classmethod
    def from_bootstrap_request(
        cls,
        request: EncodedApplicationRequest,
    ) -> OneShotPublishEnvelope:
        return cls(
            operation_key=f"bootstrap:{request.ordinal}:{request.message_type}",
            topic=request.topic,
            payload=request.payload,
            payload_sha256=request.payload_sha256,
            payload_bytes=request.payload_bytes,
            qos=request.qos,
            retain=request.retain,
        )

    @classmethod
    def from_dialogue_request(
        cls,
        request: EncodedApplicationRequest,
    ) -> OneShotPublishEnvelope:
        if request.phase != "post-start-observation" or request.ordinal not in {12, 13, 14}:
            raise ValueError("application request is not a retained post-START operation")
        return cls(
            operation_key=f"dialogue:{request.ordinal}:{request.message_type}",
            topic=request.topic,
            payload=request.payload,
            payload_sha256=request.payload_sha256,
            payload_bytes=request.payload_bytes,
            qos=request.qos,
            retain=request.retain,
        )

    @classmethod
    def from_modeling_command(
        cls,
        command: ShadowModelingCommand,
    ) -> OneShotPublishEnvelope:
        return cls(
            operation_key=f"modeling:{command.action.name.casefold()}",
            topic=command.topic,
            payload=command.command.payload,
            payload_sha256=command.payload_sha256,
            payload_bytes=command.payload_bytes,
            qos=command.qos,
            retain=command.retain,
        )

    def __post_init__(self) -> None:
        if not self.operation_key or any(
            ord(character) <= 0x20 or ord(character) > 0x7E for character in self.operation_key
        ):
            raise ValueError("publish envelope operation key is invalid")
        if self.qos != 2 or self.retain or self.automatic_retry:
            raise ValueError("publish envelope differs from the reviewed one-shot contract")
        if self.payload_bytes != len(self.payload):
            raise ValueError("publish envelope payload length mismatch")
        if not hashlib.sha256(self.payload).hexdigest() == self.payload_sha256:
            raise ValueError("publish envelope payload digest mismatch")

    def as_dict(self) -> dict[str, object]:
        return {
            "operation_key": self.operation_key,
            "topic": self.topic,
            "payload_sha256": self.payload_sha256,
            "payload_bytes": self.payload_bytes,
            "qos": self.qos,
            "retain": self.retain,
            "automatic_retry": self.automatic_retry,
        }


@dataclass(frozen=True, slots=True)
class WriteDisabledPublisherSnapshot:
    denied_attempts: int
    transport_calls: int = 0
    vendor_writes_enabled: bool = False
    publisher_armed: bool = False
    automatic_retry: bool = False

    def as_dict(self) -> dict[str, object]:
        return {
            "mode": "write-disabled",
            "denied_attempts": self.denied_attempts,
            "transport_calls": self.transport_calls,
            "vendor_writes_enabled": self.vendor_writes_enabled,
            "publisher_armed": self.publisher_armed,
            "automatic_retry": self.automatic_retry,
        }


class WriteDisabledOneShotPublisher:
    """Current-profile publish boundary that cannot call its injected sink.

    A future write-capable implementation must be a separate reviewed type.  It
    cannot be enabled by flipping a runtime boolean on this class.
    """

    def __init__(self, sink: ApplicationPublishSink) -> None:
        self._sink = sink
        self._lock = threading.Lock()
        self._denied_attempts = 0

    def publish_once(self, _envelope: OneShotPublishEnvelope) -> None:
        with self._lock:
            self._denied_attempts += 1
        raise VendorWritesDisabledError(
            "active K1 compatibility profile cannot publish vendor requests"
        )

    def snapshot(self) -> WriteDisabledPublisherSnapshot:
        with self._lock:
            return WriteDisabledPublisherSnapshot(denied_attempts=self._denied_attempts)
