from __future__ import annotations

import hmac
import math
import secrets
import threading
import time
from collections import deque
from collections.abc import Callable, Collection, Sequence
from dataclasses import dataclass

import paho.mqtt.client as mqtt
from paho.mqtt.enums import CallbackAPIVersion
from paho.mqtt.properties import Properties
from paho.mqtt.reasoncodes import ReasonCode

from k1link.device_plugins.xgrids_k1.ble.wifi_provisioning import AP_FALLBACK_IPV4
from k1link.device_plugins.xgrids_k1.mqtt import validate_private_ipv4
from k1link.device_plugins.xgrids_k1.protocol.application_bootstrap import (
    DEVICE_CONFIG_REQUEST_TOPIC,
    DEVICE_CONFIG_RESPONSE_TOPIC,
    DEVICE_INFO_REQUEST_TOPIC,
    DEVICE_INFO_RESPONSE_TOPIC,
    GET_CLOUD_CONFIG_REQUEST_TOPIC,
    GET_CLOUD_CONFIG_RESPONSE_TOPIC,
    GET_NTRIP_PROFILE_REQUEST_TOPIC,
    GET_NTRIP_PROFILE_RESPONSE_TOPIC,
    GET_RTK_ADVANCE_REQUEST_TOPIC,
    GET_RTK_ADVANCE_RESPONSE_TOPIC,
    MODELING_STATUS_REQUEST_TOPIC,
    MODELING_STATUS_RESPONSE_TOPIC,
    ApplicationBootstrapError,
    ApplicationMessageIdentity,
    LiveDeviceControlBinding,
    decode_application_message_identity,
)
from k1link.device_plugins.xgrids_k1.protocol.application_publish import (
    OneShotPublishEnvelope,
)
from k1link.device_plugins.xgrids_k1.protocol.modeling_control import (
    ModelingAction,
    ModelingProtocolError,
    decode_device_status_report,
    decode_modeling_response,
    decode_system_error_report,
)
from k1link.device_plugins.xgrids_k1.protocol.modeling_safety import (
    DEVICE_STATUS_TOPIC,
    MODELING_REQUEST_TOPIC,
)

MODELING_RESPONSE_TOPIC = "lixel/application/response/modeling"
CONTROL_KEEPALIVE_SECONDS = 60
CONTROL_CONNECT_TIMEOUT_SECONDS = 10.0
CONTROL_EXCHANGE_TIMEOUT_SECONDS = 5.0
CONTROL_LOOP_INTERVAL_SECONDS = 0.05
MAX_CONTROL_MAINTAIN_SECONDS = 30.0
MAX_CONTROL_RESPONSE_BYTES = 64 * 1024
SYSTEM_ERROR_TOPIC = "lixel/application/report/system_error"

APPLICATION_RESPONSE_TOPIC_BY_REQUEST_TOPIC = {
    DEVICE_INFO_REQUEST_TOPIC: DEVICE_INFO_RESPONSE_TOPIC,
    MODELING_STATUS_REQUEST_TOPIC: MODELING_STATUS_RESPONSE_TOPIC,
    GET_RTK_ADVANCE_REQUEST_TOPIC: GET_RTK_ADVANCE_RESPONSE_TOPIC,
    DEVICE_CONFIG_REQUEST_TOPIC: DEVICE_CONFIG_RESPONSE_TOPIC,
    GET_NTRIP_PROFILE_REQUEST_TOPIC: GET_NTRIP_PROFILE_RESPONSE_TOPIC,
    GET_CLOUD_CONFIG_REQUEST_TOPIC: GET_CLOUD_CONFIG_RESPONSE_TOPIC,
    MODELING_REQUEST_TOPIC: MODELING_RESPONSE_TOPIC,
}

# The retained LixelGO control connection issued these three SUBSCRIBE packets
# in this exact order. Point-cloud subscriptions belonged to a separate client.
CONTROL_SUBSCRIPTION_GROUPS: tuple[tuple[tuple[str, int], ...], ...] = (
    (
        ("RealtimePath", 0),
        ("RtkLioFusion", 0),
        ("RtkPoseFusion", 0),
        ("PrePathArray", 0),
        ("ScanStatus", 0),
        ("lixel/application/report/lio_pose", 0),
        (DEVICE_STATUS_TOPIC, 0),
        (SYSTEM_ERROR_TOPIC, 0),
        (DEVICE_INFO_RESPONSE_TOPIC, 0),
    ),
    (
        ("SystemStatus", 1),
        ("DeviceStatus", 1),
        ("StatisticalData", 1),
        ("lixel/application/report/modeling", 1),
        ("lixel/application/report/upgrade", 1),
    ),
    (
        ("AbnormalStatus", 2),
        ("ActionApp2DeviceResponse", 2),
        ("ActionAppSyncTimeResponse", 2),
        ("DeviceInfoResponse", 2),
        ("DeviceActivateResponse", 2),
        ("CumulativeResponse", 2),
        ("DebugStatusResponse", 2),
        ("ModelingInfoResponse", 2),
        ("ControlPointResponse", 2),
        ("GetNtripConfigResponse", 2),
        ("SetNtripConfigResponse", 2),
        ("ProjectOperationResponse", 2),
        ("PotreeConvertResponse", 2),
        ("SetProjectNoteResponse", 2),
        ("GetAppConfigResponse", 2),
        ("SetAppConfigResponse", 2),
        ("GetMqttConfigResponse", 2),
        ("SetMqttConfigResponse", 2),
        ("lixel/application/report/heartbeat", 2),
        ("lixel/calibration/response/lidar", 2),
        ("lixel/application/response/cameraConfig", 2),
        ("lixel/application/response/service", 2),
        ("lixel/application/report/pgo_progress", 2),
        ("lixel/application/response/rtk/set_satellite_system", 2),
        ("lixel/application/report/modeling/status", 2),
        ("lixel/application/response/project_file", 2),
        (GET_CLOUD_CONFIG_RESPONSE_TOPIC, 2),
        ("lixel/application/response/set_cloud_server_config", 2),
        (GET_NTRIP_PROFILE_RESPONSE_TOPIC, 2),
        ("lixel/application/response/set_ntrip_profile", 2),
        (GET_RTK_ADVANCE_RESPONSE_TOPIC, 2),
        ("lixel/application/response/set_rtk_advance", 2),
        (MODELING_STATUS_RESPONSE_TOPIC, 2),
        ("lixel/application/response/control_point", 2),
        ("lixel/application/response/service_control", 2),
        ("lixel/application/response/project_list", 2),
        ("lixel/application/response/device_control", 2),
        (DEVICE_CONFIG_RESPONSE_TOPIC, 2),
        (MODELING_RESPONSE_TOPIC, 2),
        ("lixel/application/report/control_point", 2),
        ("lixel/application/response/measure_point", 2),
        ("lixel/application/response/upgrade", 2),
    ),
)

APPLICATION_RESPONSE_TOPICS = frozenset(
    {
        DEVICE_INFO_RESPONSE_TOPIC,
        MODELING_STATUS_RESPONSE_TOPIC,
        GET_RTK_ADVANCE_RESPONSE_TOPIC,
        DEVICE_CONFIG_RESPONSE_TOPIC,
        GET_NTRIP_PROFILE_RESPONSE_TOPIC,
        GET_CLOUD_CONFIG_RESPONSE_TOPIC,
        MODELING_RESPONSE_TOPIC,
    }
)

CONTROL_REPORT_TOPICS = frozenset(
    topic
    for group in CONTROL_SUBSCRIPTION_GROUPS
    for topic, _qos in group
    if "Response" not in topic and "/response/" not in topic
)

APPLICATION_REQUEST_ALLOWLIST = frozenset(
    {
        "lixel/application/request/device_info",
        "lixel/application/request/modeling_status",
        "lixel/application/request/get_rtk_advance",
        "lixel/application/request/device_config",
        "lixel/application/request/get_ntrip_profile",
        "lixel/application/request/get_cloud_server_config",
        MODELING_REQUEST_TOPIC,
    }
)


class ApplicationMqttTransportError(RuntimeError):
    """The reviewed control transport failed before a request was attempted."""

    def __init__(self, message: str, *, reason_code: str = "transport_failure") -> None:
        super().__init__(message)
        self.reason_code = reason_code


class ApplicationCommandOutcomeUnknown(RuntimeError):
    """A request may have reached the K1 and must never be retried automatically."""

    def __init__(
        self,
        message: str,
        *,
        reason_code: str = "command_outcome_unknown",
    ) -> None:
        super().__init__(message)
        self.reason_code = reason_code


class ApplicationControlDeviceFault(RuntimeError):
    """Live status made further automatic control inadmissible."""

    def __init__(self, message: str, *, reason_code: str = "device_status_fault") -> None:
        super().__init__(message)
        self.reason_code = reason_code


@dataclass(frozen=True, slots=True)
class _ApplicationResponseExpectation:
    operation_key: str
    topic: str
    identity: ApplicationMessageIdentity
    modeling_action: ModelingAction | None = None

    @property
    def correlation_key(self) -> tuple[str, str, ModelingAction | None]:
        return (self.topic, self.identity.session_id, self.modeling_action)

    def matches(
        self,
        observed: ApplicationMessageIdentity,
        observed_modeling_action: ModelingAction | None,
    ) -> bool:
        if self.modeling_action is not observed_modeling_action:
            return False
        if not hmac.compare_digest(self.identity.session_id, observed.session_id):
            return False
        if not hmac.compare_digest(self.identity.openapi_key, observed.openapi_key):
            return False
        expected_device_id = self.identity.vendor_device_id
        return expected_device_id is None or (
            observed.vendor_device_id is not None
            and hmac.compare_digest(expected_device_id, observed.vendor_device_id)
        )


@dataclass(frozen=True, slots=True)
class ApplicationMqttTransportSnapshot:
    state: str
    connect_attempts: int
    subscribe_attempts: int
    publish_attempts: int
    qos2_completions: int
    correlated_responses: int
    ignored_known_responses: int
    late_known_responses: int
    device_status_reports: int
    system_error_reports: int
    report_decode_errors: int
    latest_device_session_state: str | None
    latest_device_project_bound: bool | None
    latest_device_init_ready: bool | None
    latest_system_error_code: int | None
    latest_system_error_state: str | None
    operation_keys_consumed: int
    clean_session: bool = False
    keepalive_seconds: int = CONTROL_KEEPALIVE_SECONDS
    automatic_reconnect: bool = False
    automatic_retry: bool = False

    def as_dict(self) -> dict[str, object]:
        return {
            "mode": "physical-acceptance-only",
            "state": self.state,
            "connect_attempts": self.connect_attempts,
            "subscribe_attempts": self.subscribe_attempts,
            "publish_attempts": self.publish_attempts,
            "qos2_completions": self.qos2_completions,
            "correlated_responses": self.correlated_responses,
            "ignored_known_responses": self.ignored_known_responses,
            "late_known_responses": self.late_known_responses,
            "device_status_reports": self.device_status_reports,
            "system_error_reports": self.system_error_reports,
            "report_decode_errors": self.report_decode_errors,
            "latest_device_session_state": self.latest_device_session_state,
            "latest_device_project_bound": self.latest_device_project_bound,
            "latest_device_init_ready": self.latest_device_init_ready,
            "latest_system_error_code": self.latest_system_error_code,
            "latest_system_error_state": self.latest_system_error_state,
            "operation_keys_consumed": self.operation_keys_consumed,
            "clean_session": self.clean_session,
            "keepalive_seconds": self.keepalive_seconds,
            "automatic_reconnect": self.automatic_reconnect,
            "automatic_retry": self.automatic_retry,
        }


class ReviewedApplicationMqttTransport:
    """Exact-profile MQTT exchange for an operator-present physical acceptance.

    Plugin v0.5.0 installs this type only behind the operator-driven continuous
    session owner. It performs one connection attempt, never reconnects,
    consumes every operation key before calling ``publish``, and permanently
    poisons itself after any unknown post-publish outcome.
    """

    def __init__(
        self,
        host: str,
        *,
        port: int = 1883,
        connect_timeout_seconds: float = CONTROL_CONNECT_TIMEOUT_SECONDS,
        exchange_timeout_seconds: float = CONTROL_EXCHANGE_TIMEOUT_SECONDS,
        allow_device_ap: bool = False,
        client_factory: Callable[[], mqtt.Client] | None = None,
        monotonic: Callable[[], float] = time.monotonic,
    ) -> None:
        self._target_ipv4 = validate_private_ipv4(host)
        if self._target_ipv4 == AP_FALLBACK_IPV4 and not allow_device_ap:
            raise ValueError("K1 access-point fallback address is not a direct-LAN control target")
        if not 1 <= port <= 65535:
            raise ValueError("port must be between 1 and 65535")
        for name, value in (
            ("connect_timeout_seconds", connect_timeout_seconds),
            ("exchange_timeout_seconds", exchange_timeout_seconds),
        ):
            if not math.isfinite(value) or value <= 0:
                raise ValueError(f"{name} must be finite and greater than zero")
        self._port = port
        self._connect_timeout_seconds = connect_timeout_seconds
        self._exchange_timeout_seconds = exchange_timeout_seconds
        self._monotonic = monotonic
        self._client_factory = client_factory
        self._client: mqtt.Client | None = None
        self._lock = threading.Lock()
        self._state = "new"
        self._connected = False
        self._subscribed = False
        self._closing = False
        self._subscription_mid: int | None = None
        self._subscription_group_index = 0
        self._completed_publish_mids: set[int] = set()
        self._messages: deque[tuple[str, bytes]] = deque()
        self._callback_error: str | None = None
        self._consumed_operation_keys: set[str] = set()
        self._known_response_expectations: dict[str, _ApplicationResponseExpectation] = {}
        self._response_operations_by_correlation: dict[
            tuple[str, str, ModelingAction | None], list[str]
        ] = {}
        self._optional_response_operations: set[str] = set()
        self._observed_response_operations: set[str] = set()
        self._connect_attempts = 0
        self._subscribe_attempts = 0
        self._publish_attempts = 0
        self._qos2_completions = 0
        self._correlated_responses = 0
        self._ignored_known_responses = 0
        self._late_known_responses = 0
        self._device_status_reports = 0
        self._system_error_reports = 0
        self._report_decode_errors = 0
        self._latest_device_session_state: str | None = None
        self._latest_device_project_bound: bool | None = None
        self._latest_device_init_ready: bool | None = None
        self._latest_device_id: str | None = None
        self._latest_device_serial: str | None = None
        self._latest_device_fault = False
        self._latest_system_error_code: int | None = None
        self._latest_system_error_state: str | None = None

    def open(self) -> ApplicationMqttTransportSnapshot:
        with self._lock:
            if self._state != "new":
                raise ApplicationMqttTransportError(
                    "control transport can be opened only once",
                    reason_code="transport_already_opened",
                )
            self._state = "connecting"
            self._connect_attempts = 1
        client = self._new_client()
        client.connect_timeout = self._connect_timeout_seconds
        self._install_callbacks(client)
        self._client = client
        try:
            result = client.connect(
                self._target_ipv4,
                port=self._port,
                keepalive=CONTROL_KEEPALIVE_SECONDS,
            )
        except (OSError, RuntimeError, ValueError) as exc:
            self._fail_before_publish("control MQTT connect call failed", exc)
        if result != mqtt.MQTT_ERR_SUCCESS:
            self._fail_before_publish("control MQTT connect call was rejected")

        deadline = self._monotonic() + self._connect_timeout_seconds
        self._drive_until(lambda: self._subscribed, deadline, post_publish=False)
        with self._lock:
            self._state = "ready"
        return self.snapshot()

    def exchange_batch_once(
        self,
        envelopes: Sequence[OneShotPublishEnvelope],
        *,
        required_response_operation_keys: Collection[str],
    ) -> dict[str, bytes]:
        batch = tuple(envelopes)
        required = frozenset(required_response_operation_keys)
        if not batch:
            raise ValueError("control exchange batch must not be empty")
        if not required and any(
            envelope.topic != "lixel/application/request/modeling_status"
            for envelope in batch
        ):
            raise ValueError("response-free exchange is limited to retained ModelingStatus reads")
        operation_keys = tuple(envelope.operation_key for envelope in batch)
        if len(set(operation_keys)) != len(operation_keys):
            raise ValueError("control exchange batch repeats an operation key")
        if not required <= frozenset(operation_keys):
            raise ValueError("required responses exceed the issued operation keys")
        if any(envelope.topic not in APPLICATION_REQUEST_ALLOWLIST for envelope in batch):
            raise ValueError("control exchange batch exceeds the reviewed request allowlist")
        expectations = tuple(self._expectation_for_envelope(envelope) for envelope in batch)
        pending = {
            expectation.correlation_key: expectation
            for expectation in expectations
            if expectation.operation_key in required
        }
        if len(pending) != len(required):
            raise ValueError(
                "required responses do not identify one exact batch operation each"
            )

        # Classify packets already delivered by the manual Paho loop before
        # consuming this batch. An exact response to an earlier issued
        # operation is harmless; an unknown session/identity remains fatal.
        self._drain_responses({}, {})
        if self._consumed_operation_keys:
            # Service packets already readable on the retained socket before
            # admitting another operation.  This is a zero-wait network pump,
            # not a timer or retry; it prevents a queued protocol deviation
            # from being hidden behind the next explicit operator action.
            self._service_once(post_publish=True)
            self._drain_responses({}, {})

        with self._lock:
            if self._consumed_operation_keys.intersection(operation_keys):
                raise ApplicationCommandOutcomeUnknown(
                    "control operation key was already consumed; retry is forbidden",
                    reason_code="operation_reuse_forbidden",
                )
            if self._state != "ready" or not self._connected or not self._subscribed:
                raise ApplicationMqttTransportError(
                    "control transport is not ready",
                    reason_code="transport_not_ready",
                )
            for expectation in expectations:
                existing_operations = self._response_operations_by_correlation.get(
                    expectation.correlation_key,
                    [],
                )
                if any(
                    not self._known_response_expectations[operation_key].matches(
                        expectation.identity,
                        expectation.modeling_action,
                    )
                    for operation_key in existing_operations
                ):
                    raise ApplicationCommandOutcomeUnknown(
                        "control response identity changed for an issued session",
                        reason_code="response_identity_changed",
                    )
                if expectation.operation_key in required:
                    # A response-free read owns its correlation only until the
                    # next exact same-identity operation is admitted.  We have
                    # already drained callbacks and given the retained socket
                    # one zero-wait service turn above, so any response already
                    # available for the optional operation has been observed.
                    # Once the newer required operation is issued, the first
                    # response after that issuance belongs to the newer
                    # operation; the unanswered optional read must not reserve
                    # FIFO ownership and swallow it.
                    superseded_optional = {
                        operation_key
                        for operation_key in existing_operations
                        if operation_key in self._optional_response_operations
                        and operation_key not in self._observed_response_operations
                    }
                    if superseded_optional:
                        existing_operations[:] = [
                            operation_key
                            for operation_key in existing_operations
                            if operation_key not in superseded_optional
                        ]
                        self._optional_response_operations.difference_update(
                            superseded_optional
                        )
                        for operation_key in superseded_optional:
                            del self._known_response_expectations[operation_key]
                self._known_response_expectations[
                    expectation.operation_key
                ] = expectation
                self._response_operations_by_correlation.setdefault(
                    expectation.correlation_key,
                    [],
                ).append(expectation.operation_key)
                if expectation.operation_key not in required:
                    self._optional_response_operations.add(expectation.operation_key)
            self._consumed_operation_keys.update(operation_keys)

        client = self._require_client()
        publish_mids: set[int] = set()
        for envelope in batch:
            with self._lock:
                self._publish_attempts += 1
            try:
                info = client.publish(
                    envelope.topic,
                    payload=envelope.payload,
                    qos=envelope.qos,
                    retain=envelope.retain,
                )
            except (OSError, RuntimeError, ValueError) as exc:
                self._fail_after_publish("control MQTT publish call failed", exc)
            if info.rc != mqtt.MQTT_ERR_SUCCESS or info.mid is None:
                self._fail_after_publish("control MQTT publish call returned an unsafe result")
            if info.mid in publish_mids:
                self._fail_after_publish("control MQTT reused a packet identifier")
            publish_mids.add(info.mid)

        responses: dict[str, bytes] = {}
        deadline = self._monotonic() + self._exchange_timeout_seconds

        def complete() -> bool:
            self._drain_responses(pending, responses)
            with self._lock:
                return publish_mids <= self._completed_publish_mids and required <= responses.keys()

        self._drive_until(complete, deadline, post_publish=True)
        # The completion predicate can become true immediately after the first
        # required response callback.  Give an already-readable second packet
        # one zero-wait service turn so a duplicate application response fails
        # closed instead of leaking into the next operator checkpoint.
        self._service_once(post_publish=True)
        self._drain_responses(pending, responses)
        with self._lock:
            self._correlated_responses += len(responses)
        return responses

    def maintain_open_for(
        self,
        duration_seconds: float,
        *,
        allowed_response_topics: Collection[str] = (),
    ) -> None:
        """Continuously service the same control socket across static initialization."""

        if (
            not isinstance(duration_seconds, (int, float))
            or isinstance(duration_seconds, bool)
            or not math.isfinite(duration_seconds)
            or not 0 < duration_seconds <= MAX_CONTROL_MAINTAIN_SECONDS
        ):
            raise ValueError("control maintain duration is outside the reviewed bound")
        allowed = frozenset(allowed_response_topics)
        if not allowed <= frozenset({MODELING_STATUS_RESPONSE_TOPIC}):
            raise ValueError("control maintain response allowlist exceeds retained dialogue")
        with self._lock:
            if self._state != "ready" or not self._connected or not self._subscribed:
                raise ApplicationMqttTransportError("control transport is not ready")
        deadline = self._monotonic() + float(duration_seconds)
        client = self._require_client()
        while self._monotonic() < deadline:
            self._discard_allowed_responses(allowed)
            with self._lock:
                callback_error = self._callback_error
            if callback_error is not None:
                self._fail_after_publish(callback_error)
            try:
                result = client.loop(timeout=CONTROL_LOOP_INTERVAL_SECONDS)
            except (OSError, RuntimeError, ValueError) as exc:
                self._fail_after_publish("control MQTT network loop failed", exc)
            if result != mqtt.MQTT_ERR_SUCCESS:
                self._fail_after_publish("control MQTT network loop returned an error")
        self._discard_allowed_responses(allowed)

    def close(self) -> None:
        with self._lock:
            if self._state == "closed":
                return
            self._closing = True
            client = self._client
        if client is not None:
            try:
                with self._lock:
                    accepted_group_count = self._subscription_group_index
                if accepted_group_count:
                    client.unsubscribe(
                        [
                            topic
                            for group in CONTROL_SUBSCRIPTION_GROUPS[:accepted_group_count]
                            for topic, _qos in group
                        ]
                    )
                client.disconnect()
            except (OSError, RuntimeError, ValueError):
                pass
        with self._lock:
            self._connected = False
            self._subscribed = False
            self._messages.clear()
            if self._state not in {"poisoned", "failed"}:
                self._state = "closed"

    def scan_initialization_complete(self, binding: LiveDeviceControlBinding) -> bool:
        """Return true only for the bound SCANNING/project/init-ready state."""

        with self._lock:
            self._require_healthy_bound_status_locked(binding)
            return (
                self._latest_device_session_state == "scanning"
                and self._latest_device_project_bound is True
                and self._latest_device_init_ready is True
            )

    def pre_start_ready(self, binding: LiveDeviceControlBinding) -> bool:
        """Return true only when the bound K1 is READY with no current project."""

        with self._lock:
            self._require_healthy_bound_status_locked(binding)
            return (
                self._latest_device_session_state == "ready"
                and self._latest_device_project_bound is False
            )

    def standby_complete(self, binding: LiveDeviceControlBinding) -> bool:
        """Return true only when the same K1 reports unbound READY after STOP."""

        with self._lock:
            self._require_healthy_bound_status_locked(binding)
            return (
                self._latest_device_session_state == "ready"
                and self._latest_device_project_bound is False
            )

    def validate_bound_status(self, binding: LiveDeviceControlBinding) -> None:
        """Fail closed on report faults or identity drift without advancing state."""

        with self._lock:
            self._require_healthy_bound_status_locked(binding)

    def snapshot(self) -> ApplicationMqttTransportSnapshot:
        with self._lock:
            return ApplicationMqttTransportSnapshot(
                state=self._state,
                connect_attempts=self._connect_attempts,
                subscribe_attempts=self._subscribe_attempts,
                publish_attempts=self._publish_attempts,
                qos2_completions=self._qos2_completions,
                correlated_responses=self._correlated_responses,
                ignored_known_responses=self._ignored_known_responses,
                late_known_responses=self._late_known_responses,
                device_status_reports=self._device_status_reports,
                system_error_reports=self._system_error_reports,
                report_decode_errors=self._report_decode_errors,
                latest_device_session_state=self._latest_device_session_state,
                latest_device_project_bound=self._latest_device_project_bound,
                latest_device_init_ready=self._latest_device_init_ready,
                latest_system_error_code=self._latest_system_error_code,
                latest_system_error_state=self._latest_system_error_state,
                operation_keys_consumed=len(self._consumed_operation_keys),
            )

    def _new_client(self) -> mqtt.Client:
        if self._client_factory is not None:
            return self._client_factory()
        client_id = f"mck1-{secrets.token_hex(7)}"
        return mqtt.Client(
            callback_api_version=CallbackAPIVersion.VERSION2,
            client_id=client_id,
            clean_session=False,
            protocol=mqtt.MQTTv311,
            reconnect_on_failure=False,
        )

    def _install_callbacks(self, client: mqtt.Client) -> None:
        def on_connect(
            callback_client: mqtt.Client,
            _userdata: object,
            _flags: mqtt.ConnectFlags,
            reason_code: ReasonCode,
            _properties: Properties | None,
        ) -> None:
            if reason_code.is_failure:
                self._set_callback_error("control MQTT broker rejected connection")
                return
            with self._lock:
                self._connected = True
            self._subscribe_next_group(callback_client)

        def on_subscribe(
            _callback_client: mqtt.Client,
            _userdata: object,
            mid: int,
            reason_codes: list[ReasonCode],
            _properties: Properties | None,
        ) -> None:
            with self._lock:
                expected_mid = self._subscription_mid
                group_index = self._subscription_group_index
            if group_index >= len(CONTROL_SUBSCRIPTION_GROUPS):
                self._set_callback_error("control MQTT received a surplus SUBACK")
                return
            expected_group = CONTROL_SUBSCRIPTION_GROUPS[group_index]
            if mid != expected_mid or len(reason_codes) != len(expected_group):
                self._set_callback_error("control MQTT received an unexpected SUBACK")
                return
            if any(reason_code.is_failure for reason_code in reason_codes):
                self._set_callback_error("control MQTT broker rejected a response subscription")
                return
            with self._lock:
                self._subscription_group_index += 1
                complete = self._subscription_group_index == len(CONTROL_SUBSCRIPTION_GROUPS)
                self._subscribed = complete
            if not complete:
                self._subscribe_next_group(_callback_client)

        def on_publish(
            _callback_client: mqtt.Client,
            _userdata: object,
            mid: int,
            reason_code: ReasonCode,
            _properties: Properties | None,
        ) -> None:
            if reason_code.is_failure:
                self._set_callback_error("control MQTT QoS2 transaction failed")
                return
            with self._lock:
                self._completed_publish_mids.add(mid)
                self._qos2_completions += 1

        def on_message(
            _callback_client: mqtt.Client,
            _userdata: object,
            message: mqtt.MQTTMessage,
        ) -> None:
            allowed = {topic for group in CONTROL_SUBSCRIPTION_GROUPS for topic, _qos in group}
            if message.topic not in allowed:
                self._set_callback_error("control MQTT received an unreviewed subscribed topic")
                return
            payload = bytes(message.payload)
            if len(payload) > MAX_CONTROL_RESPONSE_BYTES:
                self._set_callback_error("control MQTT response exceeds the reviewed bound")
                return
            if message.topic in CONTROL_REPORT_TOPICS:
                self._observe_control_report(message.topic, payload)
                with self._lock:
                    self._ignored_known_responses += 1
            else:
                with self._lock:
                    self._messages.append((message.topic, payload))

        def on_disconnect(
            _callback_client: mqtt.Client,
            _userdata: object,
            _flags: mqtt.DisconnectFlags,
            reason_code: ReasonCode,
            _properties: Properties | None,
        ) -> None:
            with self._lock:
                expected = self._closing
                self._connected = False
            if not expected or reason_code.is_failure:
                self._set_callback_error("control MQTT connection ended unexpectedly")

        client.on_connect = on_connect
        client.on_subscribe = on_subscribe
        client.on_publish = on_publish
        client.on_message = on_message
        client.on_disconnect = on_disconnect

    def _observe_control_report(self, topic: str, payload: bytes) -> None:
        if topic == DEVICE_STATUS_TOPIC:
            try:
                device_report = decode_device_status_report(payload)
            except ValueError:
                with self._lock:
                    self._report_decode_errors += 1
                return
            with self._lock:
                self._device_status_reports += 1
                self._latest_device_session_state = (
                    device_report.session_state.name.casefold()
                    if device_report.session_state is not None
                    else None
                )
                self._latest_device_project_bound = bool(device_report.project_id)
                self._latest_device_init_ready = device_report.init_ready
                self._latest_device_id = (
                    device_report.header.device_id
                    if device_report.header is not None
                    else None
                )
                self._latest_device_serial = device_report.device_sn
                self._latest_device_fault = bool(
                    device_report.session_state is not None
                    and device_report.session_state.is_fault
                )
            return
        if topic == SYSTEM_ERROR_TOPIC:
            try:
                system_error_report = decode_system_error_report(payload)
            except ValueError:
                with self._lock:
                    self._report_decode_errors += 1
                return
            with self._lock:
                self._system_error_reports += 1
                self._latest_system_error_code = system_error_report.error_code
                self._latest_system_error_state = (
                    system_error_report.session_state.name.casefold()
                    if system_error_report.session_state is not None
                    else None
                )

    def _require_healthy_bound_status_locked(
        self,
        binding: LiveDeviceControlBinding,
    ) -> None:
        if self._report_decode_errors:
            raise ApplicationControlDeviceFault(
                "K1 control report decoding failed; no further automatic action is admissible"
            )
        if self._system_error_reports or self._latest_device_fault:
            state = self._latest_system_error_state or self._latest_device_session_state
            raise ApplicationControlDeviceFault(
                f"K1 reported a control fault state: {state or 'unknown'}"
            )
        if self._device_status_reports == 0:
            return
        if self._latest_device_id is None or self._latest_device_serial is None:
            raise ApplicationControlDeviceFault(
                "K1 control status omitted the live device identity"
            )
        if (
            self._latest_device_id != binding.vendor_device_id
            or self._latest_device_serial != binding.device_serial
        ):
            raise ApplicationControlDeviceFault(
                "K1 control status identity drifted from the live DeviceInfo binding"
            )

    def _subscribe_next_group(self, client: mqtt.Client) -> None:
        with self._lock:
            group_index = self._subscription_group_index
            if group_index < len(CONTROL_SUBSCRIPTION_GROUPS):
                group = CONTROL_SUBSCRIPTION_GROUPS[group_index]
                self._subscribe_attempts += 1
            else:
                group = None
        if group is None:
            self._set_callback_error("control MQTT subscription sequence overflowed")
            return
        try:
            result, mid = client.subscribe(list(group))
        except (OSError, RuntimeError, ValueError):
            self._set_callback_error("control MQTT response subscription failed")
            return
        if result != mqtt.MQTT_ERR_SUCCESS or mid is None:
            self._set_callback_error("control MQTT response subscription was rejected")
            return
        with self._lock:
            self._subscription_mid = mid

    def _drive_until(
        self,
        predicate: Callable[[], bool],
        deadline: float,
        *,
        post_publish: bool,
    ) -> None:
        client = self._require_client()
        while not predicate():
            with self._lock:
                callback_error = self._callback_error
            if callback_error is not None:
                if post_publish:
                    self._fail_after_publish(callback_error)
                self._fail_before_publish(callback_error)
            if self._monotonic() >= deadline:
                if post_publish:
                    self._fail_after_publish("control MQTT response barrier timed out")
                self._fail_before_publish("control MQTT connection/subscription timed out")
            try:
                result = client.loop(timeout=CONTROL_LOOP_INTERVAL_SECONDS)
            except (OSError, RuntimeError, ValueError) as exc:
                if post_publish:
                    self._fail_after_publish("control MQTT network loop failed", exc)
                self._fail_before_publish("control MQTT network loop failed", exc)
            if result != mqtt.MQTT_ERR_SUCCESS:
                if post_publish:
                    self._fail_after_publish("control MQTT network loop returned an error")
                self._fail_before_publish("control MQTT network loop returned an error")

    def _service_once(self, *, post_publish: bool) -> None:
        with self._lock:
            callback_error = self._callback_error
        if callback_error is not None:
            if post_publish:
                self._fail_after_publish(callback_error)
            self._fail_before_publish(callback_error)
        client = self._require_client()
        try:
            result = client.loop(timeout=0.0)
        except (OSError, RuntimeError, ValueError) as exc:
            if post_publish:
                self._fail_after_publish("control MQTT network loop failed", exc)
            self._fail_before_publish("control MQTT network loop failed", exc)
        if result != mqtt.MQTT_ERR_SUCCESS:
            if post_publish:
                self._fail_after_publish("control MQTT network loop returned an error")
            self._fail_before_publish("control MQTT network loop returned an error")

    def _drain_responses(
        self,
        pending: dict[
            tuple[str, str, ModelingAction | None],
            _ApplicationResponseExpectation,
        ],
        responses: dict[str, bytes],
    ) -> None:
        ambiguous_message: str | None = None
        ambiguous_reason_code: str | None = None
        with self._lock:
            while self._messages:
                topic, payload = self._messages.popleft()
                try:
                    observed = decode_application_message_identity(payload)
                except ApplicationBootstrapError:
                    self._poison_locked("response identity decoding failed")
                    ambiguous_message = (
                        "control response identity could not be decoded safely"
                    )
                    ambiguous_reason_code = "response_identity_decode_failed"
                    break
                observed_modeling_action: ModelingAction | None = None
                if topic == MODELING_RESPONSE_TOPIC:
                    try:
                        observed_modeling_action = decode_modeling_response(
                            payload
                        ).action
                    except ModelingProtocolError:
                        self._poison_locked("modeling response action decoding failed")
                        ambiguous_message = (
                            "control modeling response could not be decoded safely"
                        )
                        ambiguous_reason_code = "modeling_response_decode_failed"
                        break
                key = (topic, observed.session_id, observed_modeling_action)
                expectation = pending.get(key)
                known_operations = self._response_operations_by_correlation.get(key, [])
                matching_operations = [
                    operation_key
                    for operation_key in known_operations
                    if self._known_response_expectations[operation_key].matches(
                        observed,
                        observed_modeling_action,
                    )
                ]
                next_unobserved = next(
                    (
                        operation_key
                        for operation_key in matching_operations
                        if operation_key not in self._observed_response_operations
                    ),
                    None,
                )
                if next_unobserved is not None:
                    self._observed_response_operations.add(next_unobserved)
                    if (
                        expectation is not None
                        and expectation.operation_key == next_unobserved
                    ):
                        responses[next_unobserved] = payload
                        continue
                    self._ignored_known_responses += 1
                    self._late_known_responses += 1
                    continue
                if matching_operations:
                    self._poison_locked("duplicate application response")
                    ambiguous_message = (
                        "duplicate application response made command outcome ambiguous"
                    )
                    ambiguous_reason_code = "duplicate_application_response"
                    break
                self._poison_locked("unexpected response identity made correlation ambiguous")
                ambiguous_message = (
                    "unexpected control response session or identity made command outcome ambiguous"
                )
                ambiguous_reason_code = "unexpected_response_identity"
                if expectation is not None or known_operations:
                    ambiguous_message = (
                        "control response identity mismatch made command outcome ambiguous"
                    )
                    ambiguous_reason_code = "response_identity_mismatch"
                break
        if ambiguous_message is not None:
            self.close()
            raise ApplicationCommandOutcomeUnknown(
                ambiguous_message,
                reason_code=ambiguous_reason_code or "response_correlation_failed",
            )

    def _discard_allowed_responses(self, allowed: frozenset[str]) -> None:
        del allowed
        self._drain_responses({}, {})

    @staticmethod
    def _expectation_for_envelope(
        envelope: OneShotPublishEnvelope,
    ) -> _ApplicationResponseExpectation:
        response_topic = APPLICATION_RESPONSE_TOPIC_BY_REQUEST_TOPIC.get(envelope.topic)
        if response_topic is None:
            raise ValueError("control request has no reviewed response topic")
        try:
            identity = decode_application_message_identity(envelope.payload)
        except ApplicationBootstrapError as exc:
            raise ValueError("control request identity is not decodable") from exc
        modeling_action: ModelingAction | None = None
        if response_topic == MODELING_RESPONSE_TOPIC:
            if envelope.operation_key == "modeling:start":
                modeling_action = ModelingAction.START
            elif envelope.operation_key == "modeling:stop":
                modeling_action = ModelingAction.STOP
            else:
                raise ValueError("modeling request operation key has no exact action")
        return _ApplicationResponseExpectation(
            operation_key=envelope.operation_key,
            topic=response_topic,
            identity=identity,
            modeling_action=modeling_action,
        )

    def _set_callback_error(self, message: str) -> None:
        with self._lock:
            if self._callback_error is None:
                self._callback_error = message

    def _fail_before_publish(self, message: str, cause: BaseException | None = None) -> None:
        with self._lock:
            self._state = "failed"
        self.close()
        error = ApplicationMqttTransportError(
            message,
            reason_code=self._transport_failure_reason_code(message),
        )
        if cause is not None:
            raise error from cause
        raise error

    def _fail_after_publish(self, message: str, cause: BaseException | None = None) -> None:
        with self._lock:
            self._poison_locked(message)
        self.close()
        error = ApplicationCommandOutcomeUnknown(
            f"{message}; automatic retry is forbidden until physical/status reconciliation",
            reason_code=self._transport_failure_reason_code(message),
        )
        if cause is not None:
            raise error from cause
        raise error

    def _poison_locked(self, _reason: str) -> None:
        self._state = "poisoned"

    def _require_client(self) -> mqtt.Client:
        client = self._client
        if client is None:
            raise ApplicationMqttTransportError(
                "control MQTT client is not installed",
                reason_code="mqtt_client_unavailable",
            )
        return client

    @staticmethod
    def _transport_failure_reason_code(message: str) -> str:
        reason_fragments = (
            ("connect call failed", "mqtt_connect_call_failed"),
            ("connect call was rejected", "mqtt_connect_rejected"),
            ("broker rejected connection", "mqtt_broker_rejected_connection"),
            ("connection/subscription timed out", "mqtt_connection_timeout"),
            ("response subscription", "mqtt_subscription_failed"),
            ("SUBACK", "mqtt_subscription_protocol_error"),
            ("publish call failed", "mqtt_publish_call_failed"),
            ("publish call returned", "mqtt_publish_result_unsafe"),
            ("reused a packet identifier", "mqtt_packet_identifier_reused"),
            ("QoS2 transaction failed", "mqtt_qos2_failed"),
            ("response barrier timed out", "mqtt_response_timeout"),
            ("network loop failed", "mqtt_network_loop_failed"),
            ("network loop returned", "mqtt_network_loop_failed"),
            ("connection ended unexpectedly", "mqtt_connection_ended"),
            ("unreviewed subscribed topic", "mqtt_unreviewed_topic"),
            ("response exceeds", "mqtt_response_too_large"),
        )
        return next(
            (code for fragment, code in reason_fragments if fragment in message),
            "mqtt_transport_failure",
        )
