from __future__ import annotations

from datetime import UTC, datetime

import pytest

from k1link.web.device_lifecycle import (
    AcquisitionRecord,
    OperationJournal,
    new_acquisition_id,
)


def test_operation_journal_reuses_an_idempotent_request_without_storing_input() -> None:
    journal = OperationJournal(clock=lambda: datetime(2026, 7, 16, 12, 0, tzinfo=UTC))

    first, created = journal.begin(
        "network.provision",
        idempotency_key="provision-owned-k1-once",
        device_id="device-test",
        device_session_id="device-session-test",
        deadline_seconds=45,
    )
    repeated, repeated_created = journal.begin(
        "network.provision",
        idempotency_key="provision-owned-k1-once",
        device_id="device-test",
        device_session_id="device-session-test",
        deadline_seconds=45,
    )

    assert created is True
    assert repeated_created is False
    assert repeated is first
    assert "password" not in str(journal.snapshot()).lower()


def test_operation_journal_records_ack_progress_and_terminal_result() -> None:
    journal = OperationJournal()
    operation, _ = journal.begin("acquisition.prepare", cancellable=True)

    journal.transition(
        operation.operation_id,
        "running",
        stage_code="compatibility-check",
        message_code="acquisition.compatibility_check",
    )
    journal.transition(
        operation.operation_id,
        "succeeded",
        stage_code="prepared",
        message_code="acquisition.prepared",
        result={"acquisition_id": "acq-test"},
        evidence_refs=("evidence-manifest-test",),
    )

    document = journal.get(operation.operation_id).as_dict()
    assert document["schema_version"] == "missioncore.operation-snapshot/v1alpha2"
    assert document["status"] == "succeeded"
    assert document["sequence"] == 3
    assert document["result"] == {"acquisition_id": "acq-test"}
    assert document["evidence_refs"] == ["evidence-manifest-test"]
    assert document["completed_at"] is not None

    with pytest.raises(ValueError, match="already terminal"):
        journal.transition(
            operation.operation_id,
            "failed",
            stage_code="late-failure",
            message_code="operation.failed",
        )


def test_operation_id_and_idempotency_key_cannot_be_rebound() -> None:
    journal = OperationJournal()
    operation, _ = journal.begin("device.inspect", idempotency_key="same-key")

    with pytest.raises(ValueError, match="another action"):
        journal.begin("network.provision", idempotency_key="same-key")
    with pytest.raises(ValueError, match="another action"):
        journal.begin("sensor.catalog.read", operation_id=operation.operation_id)


def test_idempotency_identity_cannot_be_reused_for_different_request_fingerprint() -> None:
    journal = OperationJournal()
    operation, _ = journal.begin(
        "network.provision",
        idempotency_key="same-request-only",
        request_fingerprint="fingerprint-a",
    )

    repeated, created = journal.begin(
        "network.provision",
        idempotency_key="same-request-only",
        request_fingerprint="fingerprint-a",
    )
    assert created is False
    assert repeated is operation

    with pytest.raises(ValueError, match="different request"):
        journal.begin(
            "network.provision",
            idempotency_key="same-request-only",
            request_fingerprint="fingerprint-b",
        )
    with pytest.raises(ValueError, match="different request"):
        journal.begin(
            "network.provision",
            operation_id=operation.operation_id,
            request_fingerprint="fingerprint-b",
        )

    assert "fingerprint-a" not in str(journal.snapshot())


def test_bounded_journal_never_evicts_an_operation_that_is_still_active() -> None:
    journal = OperationJournal(max_records=1)
    active, _ = journal.begin("acquisition.start", request_fingerprint="start")
    completed, _ = journal.begin("device.inspect", request_fingerprint="inspect")
    journal.transition(
        completed.operation_id,
        "succeeded",
        stage_code="completed",
        message_code="device.inspect.completed",
    )

    assert journal.get(active.operation_id).status == "accepted"
    assert [item["operation_id"] for item in journal.snapshot(limit=10)] == [active.operation_id]


def test_non_cancellable_operation_rejects_cancel_request() -> None:
    journal = OperationJournal()
    operation, _ = journal.begin("network.provision", cancellable=False)

    with pytest.raises(ValueError, match="not cancellable"):
        journal.request_cancel(operation.operation_id)


def test_acquisition_record_keeps_device_session_profile_and_manual_control_separate() -> None:
    acquisition = AcquisitionRecord(
        acquisition_id=new_acquisition_id(),
        device_id="device-test",
        device_session_id="device-session-test",
        compatibility_profile_id="xgrids.lixelkity-k1.fw-3.0.2.direct-lan.v1",
        control_mode="operator-manual",
        requested_streams=("spatial.point-cloud.live", "spatial.pose.live"),
        target_host="192.168.1.20",
        duration_seconds=60,
        evidence_policy="required",
    )

    acquisition.transition("prepared", message_code="acquisition.prepared")
    acquisition.transition(
        "awaiting_external_start",
        message_code="acquisition.operator_start_required",
        operator_instructions=("Дважды нажмите физическую кнопку устройства.",),
    )

    document = acquisition.as_dict()
    assert document["schema_version"] == "missioncore.acquisition-snapshot/v1alpha2"
    assert document["control_mode"] == "operator-manual"
    assert document["state"] == "awaiting_external_start"
    assert document["device_id"] != document["device_session_id"]
    assert document["operator_instructions"]
