from __future__ import annotations

from datetime import date

from checkin_cli.models import ContractStatus, Event, EventType, Safety, SafetyLevel
from checkin_cli.store import CanonicalEventSnapshot
from checkin_cli.weekly_operations import WeeklyOperationRow, customer_identity_digest
from checkin_cli.weekly_operations_correlation import (
    CorrelationAction,
    CorrelationOutcome,
    CorrelationProjection,
    CorrelationProjectionInput,
    CorrelationScope,
    project_canonical_checkin,
)
from tests._weekly_operations_correlation_support import CUSTOMER, DAY, event, snapshot

DAY_VALUE = date.fromisoformat(DAY)


def _project(
    canonical: CanonicalEventSnapshot,
    action: CorrelationAction = CorrelationAction.CHECKIN,
    rows: tuple[WeeklyOperationRow, ...] = (),
) -> CorrelationProjection:
    scope = CorrelationScope(customer_identity_digest(CUSTOMER), DAY_VALUE, action)
    return project_canonical_checkin(CorrelationProjectionInput(scope, canonical, rows))


def _assert_submitted(event_type: EventType) -> None:
    root = event("wizard_terminal_root_001", event_type)
    projection = _project(snapshot(root))
    assert projection.outcome is CorrelationOutcome.SUBMITTED
    assert projection.operation is not None
    assert projection.operation.source is not None
    assert projection.operation.source.event_id == root.event_id


def test_nutrition_terminal_root_is_submitted() -> None:
    _assert_submitted(EventType.NUTRITION_CHECKIN)


def test_legacy_morning_terminal_root_is_submitted() -> None:
    _assert_submitted(EventType.MORNING_CHECKIN)


def _assert_boundary(occurred_at: str, outcome: CorrelationOutcome) -> None:
    root = event("wizard_boundary_root_001", occurred_at=occurred_at)
    assert _project(snapshot(root)).outcome is outcome


def test_225959_is_submitted() -> None:
    _assert_boundary(f"{DAY}T22:59:59+09:00", CorrelationOutcome.SUBMITTED)


def test_230000_is_submitted() -> None:
    _assert_boundary(f"{DAY}T23:00:00+09:00", CorrelationOutcome.SUBMITTED)


def test_230001_awaits_cutoff() -> None:
    _assert_boundary(f"{DAY}T23:00:01+09:00", CorrelationOutcome.AWAITING_CUTOFF)


def test_two_corrections_select_only_active_tip() -> None:
    root = event("wizard_lineage_root_001")
    first = event("wizard_correction_gen_001", EventType.CORRECTION, supersedes=root.event_id)
    second = event("wizard_correction_gen_002", EventType.CORRECTION, supersedes=first.event_id)
    projection = _project(snapshot(root, first, second))
    assert projection.operation is not None
    assert projection.operation.source is not None
    assert projection.operation.source.event_id == second.event_id


def _assert_missing(events: tuple[Event, ...]) -> None:
    projection = _project(snapshot(*events))
    assert projection.outcome is CorrelationOutcome.MISSING
    assert projection.operation is None


def test_wrong_day_lineage_is_missing() -> None:
    _assert_missing((event("wizard_wrong_day_root_001", occurred_at="2026-08-18T08:00:00+09:00"),))


def test_unaccepted_root_is_missing() -> None:
    _assert_missing((event("wizard_unaccepted_root_001", status=ContractStatus.NEEDS_CLARIFICATION),))


def test_unaccepted_correction_is_missing() -> None:
    root = event("wizard_unaccepted_parent_1")
    child = event(
        "wizard_unaccepted_child_01",
        EventType.CORRECTION,
        supersedes=root.event_id,
        status=ContractStatus.NEEDS_CLARIFICATION,
    )
    _assert_missing((root, child))


def test_unsafe_root_is_missing() -> None:
    root = event(
        "wizard_unsafe_root_0001",
        status=ContractStatus.UNSAFE,
        safety=Safety(
            level=SafetyLevel.STOP_AND_ESCALATE,
            signals=("pain",),
            coaching_held=True,
        ),
    )
    _assert_missing((root,))


def test_superseded_root_is_missing() -> None:
    root = event("wizard_superseded_root_1")
    child = event(
        "wizard_invalid_child_0001", EventType.WORKOUT_RECORD, supersedes=root.event_id
    )
    _assert_missing((root, child))


def test_missing_payload_is_missing() -> None:
    malformed = event("wizard_missing_payload_1").model_copy(update={"check_in": None})
    _assert_missing((malformed,))


def test_orphan_correction_is_missing() -> None:
    orphan = event(
        "wizard_missing_parent_01",
        EventType.CORRECTION,
        supersedes="wizard_absent_parent_01",
    )
    _assert_missing((orphan,))


def test_multiple_active_roots_fail_closed() -> None:
    canonical = snapshot(event("wizard_duplicate_root_001"), event("wizard_duplicate_root_002"))
    assert _project(canonical).outcome is CorrelationOutcome.INVALID_LINEAGE


def test_malformed_sequence_pin_fails_closed() -> None:
    canonical = snapshot(event("wizard_digest_root_0001"))
    malformed = CanonicalEventSnapshot(
        canonical.events,
        (dict(canonical.sequence_rows[0], event_digest="f" * 64),),
    )
    assert _project(malformed).outcome is CorrelationOutcome.INVALID_CANONICAL
