from __future__ import annotations

import hashlib
import hmac
import re
import threading
from dataclasses import dataclass, field
from typing import Literal

from k1link.device_plugins.xgrids_k1.protocol.modeling_control import OPENAPI_SUCCESS
from k1link.device_plugins.xgrids_k1.protocol.protobuf_wire import (
    ProtobufWireError,
    ProtoField,
    encode_zigzag64,
    iter_fields,
)

MAX_APPLICATION_PAYLOAD_BYTES = 64 * 1024
MAX_HEADER_BYTES = 4 * 1024
MAX_TEXT_BYTES = 4 * 1024
APPLICATION_AUTHORITY_SOURCE = "owner-captured-lixelgo-application"
COMPATIBILITY_PROFILE_ID = "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2"
REVIEWED_DEVICE_MODEL = "LixelKity K1"
REVIEWED_DEVICE_TYPE = "A4"
REVIEWED_PROFILE_ATTESTATION_FAILURE = (
    "live DeviceInfo does not attest the reviewed activated "
    "LixelKity K1 (platform type A4) FW 3.0.2 profile"
)
DEVICE_CONFIG_TIME_CONTEXT = "Publish_Proto_DeviceConfig_SetTime"
SHADOW_BLOCKERS = ("vendor-writes-disabled", "publisher-not-installed")

DEVICE_INFO_REQUEST_TOPIC = "lixel/application/request/device_info"
DEVICE_INFO_RESPONSE_TOPIC = "lixel/application/response/device_info"
MODELING_STATUS_REQUEST_TOPIC = "lixel/application/request/modeling_status"
MODELING_STATUS_RESPONSE_TOPIC = "lixel/application/response/modeling_status"
GET_RTK_ADVANCE_REQUEST_TOPIC = "lixel/application/request/get_rtk_advance"
GET_RTK_ADVANCE_RESPONSE_TOPIC = "lixel/application/response/get_rtk_advance"
DEVICE_CONFIG_REQUEST_TOPIC = "lixel/application/request/device_config"
DEVICE_CONFIG_RESPONSE_TOPIC = "lixel/application/response/device_config"
GET_NTRIP_PROFILE_REQUEST_TOPIC = "lixel/application/request/get_ntrip_profile"
GET_NTRIP_PROFILE_RESPONSE_TOPIC = "lixel/application/response/get_ntrip_profile"
GET_CLOUD_CONFIG_REQUEST_TOPIC = "lixel/application/request/get_cloud_server_config"
GET_CLOUD_CONFIG_RESPONSE_TOPIC = "lixel/application/response/get_cloud_server_config"


class ApplicationBootstrapError(ValueError):
    """The recovered K1 application bootstrap contract was violated."""


class ApplicationCompatibilityProfileError(ApplicationBootstrapError):
    """A correlated DeviceInfo response does not match the selected profile."""

    reason_code = "compatibility_profile_mismatch"

    def __init__(self, binding: LiveDeviceControlBinding) -> None:
        self.observed_facts = {
            "device_model": binding.device_model,
            "platform_type": binding.device_type,
            "software_version": binding.software_version,
            "system_version": binding.system_version,
            "is_activated": binding.is_activated,
        }
        super().__init__(REVIEWED_PROFILE_ATTESTATION_FAILURE)


@dataclass(frozen=True, slots=True)
class ApplicationControlAuthority:
    """Private authority belonging to the reviewed LixelGO application profile.

    The retained application and wire evidence show one OpenAPI value shared by
    all request types.  It is not a scanner serial or a per-device enrollment.
    """

    openapi_key: str = field(repr=False)
    source: Literal["owner-captured-lixelgo-application"] = "owner-captured-lixelgo-application"
    compatibility_profile_id: Literal[
        "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2"
    ] = (
        "xgrids.lixelkity-k1.fw-3.0.2.local-network.v2"
    )

    def __post_init__(self) -> None:
        if (
            self.source != APPLICATION_AUTHORITY_SOURCE
            or self.compatibility_profile_id != COMPATIBILITY_PROFILE_ID
        ):
            raise ApplicationBootstrapError(
                "application authority does not match the reviewed K1 profile"
            )
        _validate_ascii_identity(self.openapi_key, "openapi_key")
        if len(self.openapi_key) != 36:
            raise ApplicationBootstrapError(
                "openapi_key must match the exact 36-byte reviewed application profile"
            )


@dataclass(frozen=True, slots=True)
class LiveDeviceControlBinding:
    """Identity and compatibility facts learned from the selected live K1."""

    vendor_device_id: str = field(repr=False)
    device_serial: str = field(repr=False)
    software_version: str = field(repr=False)
    system_version: str = field(repr=False)
    device_model: str = field(repr=False)
    device_type: str = field(repr=False)
    is_activated: bool

    def __post_init__(self) -> None:
        _validate_ascii_identity(self.vendor_device_id, "vendor_device_id")
        _validate_ascii_identity(self.device_serial, "device_serial")
        for name, value in (
            ("software_version", self.software_version),
            ("system_version", self.system_version),
            ("device_model", self.device_model),
            ("device_type", self.device_type),
        ):
            _validate_text(value, name)

    @property
    def matches_reviewed_firmware(self) -> bool:
        return _contains_version(self.software_version, "3.0.2") and _contains_version(
            self.system_version, "3.0.2"
        )

    @property
    def matches_reviewed_device(self) -> bool:
        """Match the exact model/type strings retained from the reviewed K1."""

        return (
            self.device_model == REVIEWED_DEVICE_MODEL
            and self.device_type == REVIEWED_DEVICE_TYPE
        )

    @property
    def ready_for_reviewed_profile(self) -> bool:
        return (
            self.is_activated
            and self.matches_reviewed_device
            and self.matches_reviewed_firmware
        )


@dataclass(frozen=True, slots=True)
class ApplicationRequestHeader:
    message_type: str
    authority: ApplicationControlAuthority = field(repr=False)
    binding: LiveDeviceControlBinding | None = field(default=None, repr=False)
    context: str | None = None

    def __post_init__(self) -> None:
        _validate_ascii_identity(self.message_type, "message_type")
        if self.context is not None:
            _validate_ascii_identity(self.context, "context")
        if len(self.session_id.encode("ascii")) > MAX_TEXT_BYTES:
            raise ApplicationBootstrapError("derived session_id exceeds configured limit")

    @property
    def session_id(self) -> str:
        device_id = self.binding.vendor_device_id if self.binding is not None else ""
        session = f"{device_id}:{self.message_type}"
        if self.context is not None:
            session = f"{session}:{self.context}"
        return session


@dataclass(frozen=True, slots=True)
class EncodedApplicationRequest:
    ordinal: int
    phase: Literal["identity-discovery", "bound-preparation", "post-start-observation"]
    message_type: str
    topic: str
    response_topic: str
    payload: bytes = field(repr=False)
    payload_sha256: str
    payload_bytes: int
    mutates_device: bool
    requires_live_binding: bool
    response_required: bool
    session_id: str = field(repr=False)
    vendor_device_id: str | None = field(repr=False)
    qos: int = 2
    retain: bool = False

    def as_dict(self) -> dict[str, object]:
        return {
            "ordinal": self.ordinal,
            "phase": self.phase,
            "message_type": self.message_type,
            "topic": self.topic,
            "response_topic": self.response_topic,
            "payload_sha256": self.payload_sha256,
            "payload_bytes": self.payload_bytes,
            "mutates_device": self.mutates_device,
            "requires_live_binding": self.requires_live_binding,
            "response_required": self.response_required,
            "qos": self.qos,
            "retain": self.retain,
        }


@dataclass(frozen=True, slots=True)
class ShadowApplicationBootstrap:
    """Exact observed pre-START request order, with no execution authority."""

    requests: tuple[EncodedApplicationRequest, ...] = field(repr=False)
    executable: bool = False
    blockers: tuple[str, ...] = SHADOW_BLOCKERS
    retry_policy: Literal["never-automatic"] = "never-automatic"

    def as_dict(self) -> dict[str, object]:
        return {
            "mode": "shadow-only",
            "request_count": len(self.requests),
            "mutation_count": sum(request.mutates_device for request in self.requests),
            "requests": [request.as_dict() for request in self.requests],
            "executable": self.executable,
            "blockers": list(self.blockers),
            "retry_policy": self.retry_policy,
        }


@dataclass(frozen=True, slots=True)
class CanonicalPostStartObservation:
    """Exact retained operations 12-14 following a successful START publish."""

    immediate_modeling_status: EncodedApplicationRequest = field(repr=False)
    post_initialization_refresh: tuple[
        EncodedApplicationRequest,
        EncodedApplicationRequest,
    ] = field(repr=False)

    @property
    def requests(self) -> tuple[EncodedApplicationRequest, ...]:
        return (self.immediate_modeling_status, *self.post_initialization_refresh)


@dataclass(frozen=True, slots=True)
class DeviceInfoResponse:
    binding: LiveDeviceControlBinding = field(repr=False)
    session_id: str = field(repr=False)
    openapi_key: str = field(repr=False)
    result_code: int


@dataclass(frozen=True, slots=True)
class ApplicationResponse:
    session_id: str = field(repr=False)
    vendor_device_id: str = field(repr=False)
    openapi_key: str = field(repr=False)
    result_code: int


@dataclass(frozen=True, slots=True)
class ApplicationMessageIdentity:
    """Redacted identity used to route one protobuf request/response safely."""

    vendor_device_id: str | None = field(repr=False)
    session_id: str = field(repr=False)
    openapi_key: str = field(repr=False)


@dataclass(frozen=True, slots=True)
class ApplicationBootstrapSnapshot:
    current_batch: int
    total_batches: int
    batch_issued: bool
    pending_response_topics: tuple[str, ...]
    live_binding_observed: bool
    bootstrap_complete: bool
    executable: bool = False
    vendor_writes_enabled: bool = False

    def as_dict(self) -> dict[str, object]:
        return {
            "mode": "shadow-only",
            "current_batch": self.current_batch,
            "total_batches": self.total_batches,
            "batch_issued": self.batch_issued,
            "pending_response_topics": list(self.pending_response_topics),
            "live_binding_observed": self.live_binding_observed,
            "bootstrap_complete": self.bootstrap_complete,
            "executable": self.executable,
            "vendor_writes_enabled": self.vendor_writes_enabled,
        }


def build_shadow_bootstrap(
    authority: ApplicationControlAuthority,
    binding: LiveDeviceControlBinding,
    *,
    epoch_seconds: int,
    timezone_name: str,
) -> ShadowApplicationBootstrap:
    """Build the ten pre-START requests observed in the clean LixelGO cycle.

    The first three requests intentionally omit the vendor device ID.  The
    remaining requests are bound to identity returned by ``DeviceInfoResponse``.
    The only preparatory mutation is the observed clock/timezone sync.
    """

    if not binding.ready_for_reviewed_profile:
        raise ApplicationCompatibilityProfileError(binding)
    if not isinstance(epoch_seconds, int) or isinstance(epoch_seconds, bool):
        raise ApplicationBootstrapError("epoch_seconds must be an integer")
    if epoch_seconds < 0 or epoch_seconds > 0x7FFF_FFFF_FFFF_FFFF:
        raise ApplicationBootstrapError("epoch_seconds is outside recovered int64 range")
    _validate_text(timezone_name, "timezone_name")

    specs = (
        (
            "identity-discovery",
            "DeviceInfoRequest",
            DEVICE_INFO_REQUEST_TOPIC,
            DEVICE_INFO_RESPONSE_TOPIC,
            None,
            False,
            True,
        ),
        (
            "identity-discovery",
            "ModelingStatusRequest",
            MODELING_STATUS_REQUEST_TOPIC,
            MODELING_STATUS_RESPONSE_TOPIC,
            None,
            False,
            False,
        ),
        (
            "identity-discovery",
            "GetRtkAdvanceRequest",
            GET_RTK_ADVANCE_REQUEST_TOPIC,
            GET_RTK_ADVANCE_RESPONSE_TOPIC,
            None,
            False,
            True,
        ),
        (
            "bound-preparation",
            "DeviceConfigRequest",
            DEVICE_CONFIG_REQUEST_TOPIC,
            DEVICE_CONFIG_RESPONSE_TOPIC,
            DEVICE_CONFIG_TIME_CONTEXT,
            True,
            True,
        ),
        (
            "bound-preparation",
            "DeviceInfoRequest",
            DEVICE_INFO_REQUEST_TOPIC,
            DEVICE_INFO_RESPONSE_TOPIC,
            None,
            False,
            True,
        ),
        (
            "bound-preparation",
            "GetRtkAdvanceRequest",
            GET_RTK_ADVANCE_REQUEST_TOPIC,
            GET_RTK_ADVANCE_RESPONSE_TOPIC,
            None,
            False,
            True,
        ),
        (
            "bound-preparation",
            "GetNtripProfileRequest",
            GET_NTRIP_PROFILE_REQUEST_TOPIC,
            GET_NTRIP_PROFILE_RESPONSE_TOPIC,
            None,
            False,
            True,
        ),
        (
            "bound-preparation",
            "GetCloudServerConfigRequest",
            GET_CLOUD_CONFIG_REQUEST_TOPIC,
            GET_CLOUD_CONFIG_RESPONSE_TOPIC,
            None,
            False,
            True,
        ),
        (
            "bound-preparation",
            "GetRtkAdvanceRequest",
            GET_RTK_ADVANCE_REQUEST_TOPIC,
            GET_RTK_ADVANCE_RESPONSE_TOPIC,
            None,
            False,
            True,
        ),
        (
            "bound-preparation",
            "DeviceInfoRequest",
            DEVICE_INFO_REQUEST_TOPIC,
            DEVICE_INFO_RESPONSE_TOPIC,
            None,
            False,
            True,
        ),
    )
    requests: list[EncodedApplicationRequest] = []
    for ordinal, (
        phase,
        message_type,
        topic,
        response_topic,
        context,
        mutates,
        response_required,
    ) in enumerate(specs, start=1):
        request_binding = None if ordinal <= 3 else binding
        header = ApplicationRequestHeader(
            message_type=message_type,
            authority=authority,
            binding=request_binding,
            context=context,
        )
        body = b""
        if message_type == "DeviceConfigRequest":
            time_config = _varint_field(1, encode_zigzag64(epoch_seconds)) + _text_field(
                2, timezone_name
            )
            body = _bytes_field(4, time_config)
        payload = _bytes_field(1, _encode_header(header)) + body
        _check_payload(payload, "application request")
        requests.append(
            EncodedApplicationRequest(
                ordinal=ordinal,
                phase=phase,  # type: ignore[arg-type]
                message_type=message_type,
                topic=topic,
                response_topic=response_topic,
                payload=payload,
                payload_sha256=hashlib.sha256(payload).hexdigest(),
                payload_bytes=len(payload),
                mutates_device=mutates,
                requires_live_binding=ordinal > 3,
                response_required=response_required,
                session_id=header.session_id,
                vendor_device_id=(
                    request_binding.vendor_device_id if request_binding is not None else None
                ),
            )
        )
    return ShadowApplicationBootstrap(tuple(requests))


def build_canonical_post_start_observation(
    authority: ApplicationControlAuthority,
    binding: LiveDeviceControlBinding,
) -> CanonicalPostStartObservation:
    """Build retained operations 12-14 without granting publish authority."""

    if not binding.ready_for_reviewed_profile:
        raise ApplicationCompatibilityProfileError(binding)

    def build(
        ordinal: Literal[12, 13, 14],
        message_type: Literal["DeviceInfoRequest", "ModelingStatusRequest"],
        topic: str,
        response_topic: str,
        *,
        response_required: bool,
    ) -> EncodedApplicationRequest:
        header = ApplicationRequestHeader(
            message_type=message_type,
            authority=authority,
            binding=binding,
        )
        payload = _bytes_field(1, _encode_header(header))
        _check_payload(payload, "post-START application request")
        return EncodedApplicationRequest(
            ordinal=ordinal,
            phase="post-start-observation",
            message_type=message_type,
            topic=topic,
            response_topic=response_topic,
            payload=payload,
            payload_sha256=hashlib.sha256(payload).hexdigest(),
            payload_bytes=len(payload),
            mutates_device=False,
            requires_live_binding=True,
            response_required=response_required,
            session_id=header.session_id,
            vendor_device_id=binding.vendor_device_id,
        )

    immediate = build(
        12,
        "ModelingStatusRequest",
        MODELING_STATUS_REQUEST_TOPIC,
        MODELING_STATUS_RESPONSE_TOPIC,
        response_required=False,
    )
    refresh = (
        build(
            13,
            "DeviceInfoRequest",
            DEVICE_INFO_REQUEST_TOPIC,
            DEVICE_INFO_RESPONSE_TOPIC,
            response_required=True,
        ),
        build(
            14,
            "ModelingStatusRequest",
            MODELING_STATUS_REQUEST_TOPIC,
            MODELING_STATUS_RESPONSE_TOPIC,
            response_required=True,
        ),
    )
    return CanonicalPostStartObservation(immediate, refresh)


def decode_and_bind_device_info_response(
    payload: bytes,
    authority: ApplicationControlAuthority,
    *,
    expected_session_id: str = ":DeviceInfoRequest",
    expected_vendor_device_id: str | None = None,
) -> DeviceInfoResponse:
    """Correlate the bootstrap response and derive a live per-device binding."""

    _check_payload(payload, "DeviceInfoResponse")
    top = _unique_fields(payload, "DeviceInfoResponse", max_fields=64)
    header = _unique_fields(_required_bytes(top, 1, "response.header"), "header", max_fields=32)
    device_id = _identity_field(header, 4, "header.device_id")
    session_id = _identity_field(header, 5, "header.session_id")
    openapi_key = _identity_field(header, 6, "header.openapi_key")
    if not hmac.compare_digest(session_id, expected_session_id):
        raise ApplicationBootstrapError("DeviceInfoResponse session correlation failed")
    if expected_vendor_device_id is not None and not hmac.compare_digest(
        device_id, expected_vendor_device_id
    ):
        raise ApplicationBootstrapError("DeviceInfoResponse vendor identity mismatch")
    if not hmac.compare_digest(openapi_key, authority.openapi_key):
        raise ApplicationBootstrapError("DeviceInfoResponse application authority mismatch")

    error = _unique_fields(_required_bytes(top, 15, "response.error"), "error", max_fields=8)
    result_code = _required_uint(error, 1, "response.error.code")
    if result_code != OPENAPI_SUCCESS:
        raise ApplicationBootstrapError(
            f"DeviceInfoResponse rejected with result code {result_code}"
        )

    device_info = _unique_fields(
        _required_bytes(top, 2, "response.device_info"), "device_info", max_fields=32
    )
    base_info = _unique_fields(
        _required_bytes(device_info, 2, "device_info.base_info"),
        "base_info",
        max_fields=32,
        allow_repeated={11},
    )
    working_status = _unique_fields(
        _required_bytes(device_info, 7, "device_info.working_status"),
        "working_status",
        max_fields=16,
    )
    binding = LiveDeviceControlBinding(
        vendor_device_id=device_id,
        device_serial=_required_text(base_info, 7, "base_info.device_sn"),
        software_version=_required_text(base_info, 2, "base_info.software_version"),
        system_version=_required_text(base_info, 3, "base_info.system_version"),
        device_model=_required_text(base_info, 6, "base_info.device_model"),
        device_type=_required_text(base_info, 8, "base_info.device_type"),
        is_activated=bool(_required_bool(working_status, 1, "working_status.is_activated")),
    )
    return DeviceInfoResponse(binding, session_id, openapi_key, result_code)


def correlate_application_response(
    payload: bytes,
    expected: EncodedApplicationRequest,
    authority: ApplicationControlAuthority,
    *,
    live_binding: LiveDeviceControlBinding | None = None,
) -> ApplicationResponse:
    """Correlate a non-DeviceInfo response to an exact emitted request."""

    expected_device_id = expected.vendor_device_id
    if expected_device_id is None and live_binding is not None:
        expected_device_id = live_binding.vendor_device_id
    if expected_device_id is None:
        raise ApplicationBootstrapError(
            "generic response correlation requires live DeviceInfo identity"
        )
    _check_payload(payload, "application response")
    top = _selected_unique_fields(
        payload,
        "application response",
        max_fields=64,
        selected={1, 15},
    )
    header = _selected_unique_fields(
        _required_bytes(top, 1, "response.header"),
        "header",
        max_fields=32,
        selected={4, 5, 6},
    )
    device_id = _identity_field(header, 4, "header.device_id")
    session_id = _identity_field(header, 5, "header.session_id")
    openapi_key = _identity_field(header, 6, "header.openapi_key")
    if not hmac.compare_digest(device_id, expected_device_id):
        raise ApplicationBootstrapError("application response vendor identity mismatch")
    if not hmac.compare_digest(session_id, expected.session_id):
        raise ApplicationBootstrapError("application response session correlation failed")
    if not hmac.compare_digest(openapi_key, authority.openapi_key):
        raise ApplicationBootstrapError("application response authority mismatch")

    error = _selected_unique_fields(
        _required_bytes(top, 15, "response.error"),
        "error",
        max_fields=8,
        selected={1, 2},
    )
    result_code = _required_uint(error, 1, "response.error.code")
    if result_code != OPENAPI_SUCCESS:
        raise ApplicationBootstrapError(
            f"application response rejected with result code {result_code}"
        )
    return ApplicationResponse(session_id, device_id, openapi_key, result_code)


def decode_application_message_identity(payload: bytes) -> ApplicationMessageIdentity:
    """Decode only the common header identity without exposing message bodies."""

    _check_payload(payload, "application message")
    top = _selected_unique_fields(
        payload,
        "application message",
        max_fields=64,
        selected={1},
    )
    header = _selected_unique_fields(
        _required_bytes(top, 1, "message.header"),
        "header",
        max_fields=32,
        selected={4, 5, 6},
    )
    return ApplicationMessageIdentity(
        vendor_device_id=(
            _identity_field(header, 4, "header.device_id") if 4 in header else None
        ),
        session_id=_identity_field(header, 5, "header.session_id"),
        openapi_key=_identity_field(header, 6, "header.openapi_key"),
    )


class ShadowApplicationBootstrapOrchestrator:
    """Advance the retained dialogue only across observed response barriers.

    DeviceInfo is the sole identity-binding barrier.  After it succeeds, the
    retained captures publish ordinals 2-6 in order without inventing a barrier
    between unbound ordinal 3 and bound ordinals 4-6.  Their exact responses
    are correlated by operation/session identity before the operator may
    advance.  ``ModelingStatusRequest`` ordinal 2 remains response-free;
    readiness is a separate live DeviceStatus gate.
    """

    _BATCH_ORDINALS = ((1,), (2, 3, 4, 5, 6), (7,), (8, 9, 10))

    def __init__(
        self,
        authority: ApplicationControlAuthority,
        *,
        epoch_seconds: int,
        timezone_name: str,
    ) -> None:
        self._lock = threading.Lock()
        self._authority = authority
        self._epoch_seconds = epoch_seconds
        self._timezone_name = timezone_name
        self._binding: LiveDeviceControlBinding | None = None
        self._plan: ShadowApplicationBootstrap | None = None
        self._batch_index = 0
        self._batch_issued = False
        self._pending: dict[str, EncodedApplicationRequest] = {}
        self._complete = False
        self._initial_request = _build_initial_device_info_request(authority)

    def snapshot(self) -> ApplicationBootstrapSnapshot:
        with self._lock:
            return ApplicationBootstrapSnapshot(
                current_batch=min(self._batch_index + 1, len(self._BATCH_ORDINALS)),
                total_batches=len(self._BATCH_ORDINALS),
                batch_issued=self._batch_issued,
                pending_response_topics=tuple(
                    sorted(request.response_topic for request in self._pending.values())
                ),
                live_binding_observed=self._binding is not None,
                bootstrap_complete=self._complete,
            )

    @property
    def binding(self) -> LiveDeviceControlBinding | None:
        with self._lock:
            return self._binding

    def next_batch(self) -> tuple[EncodedApplicationRequest, ...]:
        """Return the next batch once; repeated access fails closed."""

        with self._lock:
            if self._complete:
                raise ApplicationBootstrapError("application bootstrap is already complete")
            if self._batch_issued:
                raise ApplicationBootstrapError(
                    "current application bootstrap batch was already issued"
                )
            requests: tuple[EncodedApplicationRequest, ...]
            if self._batch_index == 0:
                requests = (self._initial_request,)
            else:
                if self._plan is None:
                    raise ApplicationBootstrapError("live DeviceInfo binding is missing")
                requests = tuple(
                    self._plan.requests[ordinal - 1]
                    for ordinal in self._BATCH_ORDINALS[self._batch_index]
                )
            pending = {
                self._operation_key(request): request
                for request in requests
                if request.response_required
            }
            if not pending:
                raise ApplicationBootstrapError("bootstrap batch has no recovered response barrier")
            self._pending = pending
            self._batch_issued = True
            return requests

    def accept_response(self, operation_key: str, payload: bytes) -> None:
        """Correlate one required response and unlock only the next batch."""

        with self._lock:
            if not self._batch_issued:
                raise ApplicationBootstrapError("no application bootstrap batch is in flight")
            expected = self._pending.get(operation_key)
            if expected is None:
                raise ApplicationBootstrapError(
                    "application response is not required by the current batch"
                )

            if expected.ordinal == 1:
                response = decode_and_bind_device_info_response(payload, self._authority)
                binding = response.binding
                plan = build_shadow_bootstrap(
                    self._authority,
                    binding,
                    epoch_seconds=self._epoch_seconds,
                    timezone_name=self._timezone_name,
                )
                if plan.requests[0].payload != self._initial_request.payload:
                    raise ApplicationBootstrapError(
                        "bound bootstrap plan changed the emitted identity request"
                    )
                self._binding = binding
                self._plan = plan
            else:
                if self._binding is None:
                    raise ApplicationBootstrapError("live DeviceInfo binding is missing")
                if expected.message_type == "DeviceInfoRequest":
                    response = decode_and_bind_device_info_response(
                        payload,
                        self._authority,
                        expected_session_id=expected.session_id,
                        expected_vendor_device_id=self._binding.vendor_device_id,
                    )
                    if response.binding != self._binding:
                        raise ApplicationBootstrapError(
                            "live DeviceInfo facts changed during bootstrap"
                        )
                else:
                    correlate_application_response(
                        payload,
                        expected,
                        self._authority,
                        live_binding=self._binding,
                    )

            del self._pending[operation_key]
            if self._pending:
                return
            self._batch_issued = False
            self._batch_index += 1
            if self._batch_index == len(self._BATCH_ORDINALS):
                self._complete = True

    @staticmethod
    def _operation_key(request: EncodedApplicationRequest) -> str:
        return f"bootstrap:{request.ordinal}:{request.message_type}"


def _build_initial_device_info_request(
    authority: ApplicationControlAuthority,
) -> EncodedApplicationRequest:
    header = ApplicationRequestHeader(
        message_type="DeviceInfoRequest",
        authority=authority,
    )
    payload = _bytes_field(1, _encode_header(header))
    _check_payload(payload, "application request")
    return EncodedApplicationRequest(
        ordinal=1,
        phase="identity-discovery",
        message_type="DeviceInfoRequest",
        topic=DEVICE_INFO_REQUEST_TOPIC,
        response_topic=DEVICE_INFO_RESPONSE_TOPIC,
        payload=payload,
        payload_sha256=hashlib.sha256(payload).hexdigest(),
        payload_bytes=len(payload),
        mutates_device=False,
        requires_live_binding=False,
        response_required=True,
        session_id=header.session_id,
        vendor_device_id=None,
    )


def _encode_header(header: ApplicationRequestHeader) -> bytes:
    parts: list[bytes] = []
    if header.binding is not None:
        parts.append(_text_field(4, header.binding.vendor_device_id))
    parts.extend(
        (
            _text_field(5, header.session_id),
            _text_field(6, header.authority.openapi_key),
        )
    )
    payload = b"".join(parts)
    if len(payload) > MAX_HEADER_BYTES:
        raise ApplicationBootstrapError("encoded request header exceeds configured limit")
    return payload


def _contains_version(value: str, expected: str) -> bool:
    return re.search(rf"(?<!\d){re.escape(expected)}(?!\d)", value) is not None


def _validate_ascii_identity(value: object, name: str) -> None:
    if not isinstance(value, str) or not value:
        raise ApplicationBootstrapError(f"{name} must be a non-empty string")
    try:
        encoded = value.encode("ascii")
    except UnicodeEncodeError as exc:
        raise ApplicationBootstrapError(f"{name} must use printable ASCII") from exc
    if len(encoded) > MAX_TEXT_BYTES:
        raise ApplicationBootstrapError(f"{name} exceeds configured limit")
    if any(byte <= 0x20 or byte > 0x7E for byte in encoded):
        raise ApplicationBootstrapError(f"{name} must use printable ASCII without spaces")


def _validate_text(value: object, name: str) -> None:
    if not isinstance(value, str) or not value:
        raise ApplicationBootstrapError(f"{name} must be a non-empty string")
    encoded = value.encode("utf-8")
    if len(encoded) > MAX_TEXT_BYTES:
        raise ApplicationBootstrapError(f"{name} exceeds configured limit")
    if any(ord(character) < 0x20 or ord(character) == 0x7F for character in value):
        raise ApplicationBootstrapError(f"{name} contains a control character")


def _check_payload(payload: bytes, name: str) -> None:
    if len(payload) > MAX_APPLICATION_PAYLOAD_BYTES:
        raise ApplicationBootstrapError(f"{name} exceeds configured limit")


def _unique_fields(
    payload: bytes,
    name: str,
    *,
    max_fields: int,
    allow_repeated: set[int] | None = None,
) -> dict[int, ProtoField]:
    result: dict[int, ProtoField] = {}
    repeated = allow_repeated or set()
    try:
        for item in iter_fields(payload, max_fields=max_fields):
            if item.number in result and item.number not in repeated:
                raise ApplicationBootstrapError(f"{name} field {item.number} is duplicated")
            result[item.number] = item
    except ProtobufWireError as exc:
        raise ApplicationBootstrapError(f"invalid {name}: {exc}") from exc
    return result


def _selected_unique_fields(
    payload: bytes,
    name: str,
    *,
    max_fields: int,
    selected: set[int],
) -> dict[int, ProtoField]:
    result: dict[int, ProtoField] = {}
    try:
        for item in iter_fields(payload, max_fields=max_fields):
            if item.number not in selected:
                continue
            if item.number in result:
                raise ApplicationBootstrapError(f"{name} field {item.number} is duplicated")
            result[item.number] = item
    except ProtobufWireError as exc:
        raise ApplicationBootstrapError(f"invalid {name}: {exc}") from exc
    return result


def _required_bytes(fields: dict[int, ProtoField], number: int, name: str) -> bytes:
    item = fields.get(number)
    if item is None or item.wire_type != 2 or not isinstance(item.value, bytes):
        raise ApplicationBootstrapError(f"{name} is missing or has the wrong wire type")
    return item.value


def _required_text(fields: dict[int, ProtoField], number: int, name: str) -> str:
    raw = _required_bytes(fields, number, name)
    if len(raw) > MAX_TEXT_BYTES:
        raise ApplicationBootstrapError(f"{name} exceeds configured limit")
    try:
        value = raw.decode("utf-8")
    except UnicodeDecodeError as exc:
        raise ApplicationBootstrapError(f"{name} is not valid UTF-8") from exc
    _validate_text(value, name)
    return value


def _identity_field(fields: dict[int, ProtoField], number: int, name: str) -> str:
    value = _required_text(fields, number, name)
    _validate_ascii_identity(value, name)
    return value


def _required_uint(fields: dict[int, ProtoField], number: int, name: str) -> int:
    item = fields.get(number)
    if item is None or item.wire_type != 0 or not isinstance(item.value, int):
        raise ApplicationBootstrapError(f"{name} is missing or has the wrong wire type")
    return item.value


def _required_bool(fields: dict[int, ProtoField], number: int, name: str) -> int:
    value = _required_uint(fields, number, name)
    if value not in (0, 1):
        raise ApplicationBootstrapError(f"{name} is not a protobuf boolean")
    return value


def _varint(value: int) -> bytes:
    encoded = bytearray()
    while value > 0x7F:
        encoded.append((value & 0x7F) | 0x80)
        value >>= 7
    encoded.append(value)
    return bytes(encoded)


def _key(number: int, wire_type: int) -> bytes:
    return _varint((number << 3) | wire_type)


def _varint_field(number: int, value: int) -> bytes:
    return _key(number, 0) + _varint(value)


def _bytes_field(number: int, value: bytes) -> bytes:
    return _key(number, 2) + _varint(len(value)) + value


def _text_field(number: int, value: str) -> bytes:
    return _bytes_field(number, value.encode("utf-8"))
