from __future__ import annotations

from dataclasses import dataclass
from enum import StrEnum

from k1link.device_plugins.xgrids_k1.protocol.modeling_control import (
    DeviceStatusReport,
    SessionState,
)


class AcquisitionPhase(StrEnum):
    UNOBSERVED = "unobserved"
    READY = "ready"
    CALIBRATING = "calibrating"
    SCANNING = "scanning"
    STOPPING = "stopping"
    SCAN_OVER_UNVERIFIED = "scan_over_unverified"
    FAULT = "fault"
    UNKNOWN = "unknown"


class SaveEvidence(StrEnum):
    """Evidence levels seen in status reports; none means durable completion."""

    NONE = "none"
    SCAN_OVER_OBSERVED = "scan_over_observed"
    SCAN_OVER_THEN_READY_OBSERVED = "scan_over_then_ready_observed"
    FAULT_BEFORE_DURABLE_CONFIRMATION = "fault_before_durable_confirmation"


@dataclass(frozen=True, slots=True)
class AcquisitionSnapshot:
    phase: AcquisitionPhase
    session_state: SessionState | None
    modeling_state_code: int | None
    init_ready: bool
    project_bound: bool
    save_evidence: SaveEvidence

    @property
    def durable_save_complete(self) -> bool:
        """Stay false until the physical save gate and artifacts are proven."""

        return False


class DeviceAcquisitionStateMachine:
    """Observe K1 lifecycle reports without asserting an unproved save result.

    The reviewed physical runs prove Ready -> ScanStarting -> Scanning and
    Scanning -> ScanStopping.  ``ScanOver`` exists in the recovered enum, but a
    physical stop run has not yet demonstrated the complete ScanOver/Ready plus
    stable-artifact sequence.  Consequently every snapshot is fail-closed for
    durable save completion.
    """

    def __init__(self) -> None:
        self._scan_over_observed = False
        self._ready_after_scan_over_observed = False
        self._snapshot = AcquisitionSnapshot(
            phase=AcquisitionPhase.UNOBSERVED,
            session_state=None,
            modeling_state_code=None,
            init_ready=False,
            project_bound=False,
            save_evidence=SaveEvidence.NONE,
        )

    @property
    def snapshot(self) -> AcquisitionSnapshot:
        return self._snapshot

    def observe(self, report: DeviceStatusReport) -> AcquisitionSnapshot:
        state = report.session_state

        # A new start invalidates evidence retained from a previous acquisition.
        if state is SessionState.SCAN_STARTING or (
            state is SessionState.SCANNING
            and self._snapshot.session_state in {None, SessionState.READY, SessionState.SCAN_OVER}
        ):
            self._scan_over_observed = False
            self._ready_after_scan_over_observed = False

        if state is SessionState.SCAN_OVER:
            self._scan_over_observed = True
        elif state is SessionState.READY and self._scan_over_observed:
            self._ready_after_scan_over_observed = True

        phase = _phase_for(state)
        if state is not None and state.is_fault:
            evidence = SaveEvidence.FAULT_BEFORE_DURABLE_CONFIRMATION
        elif self._ready_after_scan_over_observed:
            evidence = SaveEvidence.SCAN_OVER_THEN_READY_OBSERVED
        elif self._scan_over_observed:
            evidence = SaveEvidence.SCAN_OVER_OBSERVED
        else:
            evidence = SaveEvidence.NONE

        self._snapshot = AcquisitionSnapshot(
            phase=phase,
            session_state=state,
            modeling_state_code=report.modeling_state_code,
            init_ready=report.init_ready,
            project_bound=bool(report.project_id),
            save_evidence=evidence,
        )
        return self._snapshot


def _phase_for(state: SessionState | None) -> AcquisitionPhase:
    if state is None:
        return AcquisitionPhase.UNKNOWN
    if state is SessionState.READY:
        return AcquisitionPhase.READY
    if state is SessionState.SCAN_STARTING:
        return AcquisitionPhase.CALIBRATING
    if state is SessionState.SCANNING:
        return AcquisitionPhase.SCANNING
    if state is SessionState.SCAN_STOPPING:
        return AcquisitionPhase.STOPPING
    if state is SessionState.SCAN_OVER:
        return AcquisitionPhase.SCAN_OVER_UNVERIFIED
    if state.is_fault:
        return AcquisitionPhase.FAULT
    return AcquisitionPhase.UNKNOWN
