"""T3.1 伏笔状态机纯函数单测（ARCH §6.2）。

确定性纯函数，无 IO：覆盖全转移（登记/首次照应/完成回收）+ OVERDUE 到期判据边界
（current ==/</> expected_close_to；CLOSED 不被覆盖；无 expected_close_to 不 OVERDUE）+
非法转移显式处理。
"""

from __future__ import annotations

import pytest
from ww_core.domain.foreshadow_state import (
    CLOSED,
    OPEN,
    OVERDUE,
    PARTIAL,
    ForeshadowStatus,
    InvalidTransition,
    apply_overdue_scan,
    is_overdue,
    transition,
)

# ---- 合法转移（登记→OPEN→PARTIAL→CLOSED）----


def test_open_to_partial() -> None:
    assert transition(OPEN, PARTIAL) == PARTIAL


def test_partial_to_closed() -> None:
    assert transition(PARTIAL, CLOSED) == CLOSED


def test_open_to_closed_direct_recover() -> None:
    # 一步回收（埋后直接收）也合法
    assert transition(OPEN, CLOSED) == CLOSED


def test_overdue_to_closed_recover_after_due() -> None:
    # 逾期后仍可回收
    assert transition(OVERDUE, CLOSED) == CLOSED


def test_overdue_to_partial_partial_recover_after_due() -> None:
    assert transition(OVERDUE, PARTIAL) == PARTIAL


# ---- 非法转移显式拒绝 ----


def test_closed_cannot_reopen() -> None:
    with pytest.raises(InvalidTransition):
        transition(CLOSED, OPEN)


def test_closed_cannot_become_overdue_via_transition() -> None:
    with pytest.raises(InvalidTransition):
        transition(CLOSED, OVERDUE)


def test_closed_cannot_become_partial() -> None:
    with pytest.raises(InvalidTransition):
        transition(CLOSED, PARTIAL)


def test_partial_cannot_go_back_to_open() -> None:
    with pytest.raises(InvalidTransition):
        transition(PARTIAL, OPEN)


def test_same_status_is_noop_allowed() -> None:
    # 幂等：同态转移允许（看板手动设回同值不报错）
    assert transition(OPEN, OPEN) == OPEN
    assert transition(PARTIAL, PARTIAL) == PARTIAL


# ---- is_overdue 判据边界 ----


def test_is_overdue_strictly_past_window() -> None:
    assert is_overdue(current_chapter=11, expected_close_to=10, status=OPEN) is True


def test_is_overdue_at_window_boundary_not_overdue() -> None:
    # current == expected_close_to：仍在窗口内，未逾期
    assert is_overdue(current_chapter=10, expected_close_to=10, status=OPEN) is False


def test_is_overdue_before_window_not_overdue() -> None:
    assert is_overdue(current_chapter=5, expected_close_to=10, status=OPEN) is False


def test_is_overdue_closed_never_overdue() -> None:
    assert is_overdue(current_chapter=99, expected_close_to=10, status=CLOSED) is False


def test_is_overdue_partial_can_be_overdue() -> None:
    assert is_overdue(current_chapter=11, expected_close_to=10, status=PARTIAL) is True


def test_is_overdue_without_window_never_overdue() -> None:
    # 无 expected_close_to → 永不逾期
    assert is_overdue(current_chapter=999, expected_close_to=None, status=OPEN) is False


def test_is_overdue_already_overdue_stays() -> None:
    assert is_overdue(current_chapter=11, expected_close_to=10, status=OVERDUE) is True


# ---- apply_overdue_scan ----


def test_apply_overdue_scan_sets_overdue_when_due() -> None:
    new = apply_overdue_scan(OPEN, current_chapter=11, expected_close_to=10)
    assert new == OVERDUE


def test_apply_overdue_scan_keeps_status_when_not_due() -> None:
    assert apply_overdue_scan(OPEN, current_chapter=10, expected_close_to=10) == OPEN


def test_apply_overdue_scan_does_not_touch_closed() -> None:
    # CLOSED 不被 OVERDUE 覆盖（不变量）
    assert apply_overdue_scan(CLOSED, current_chapter=99, expected_close_to=10) == CLOSED


def test_apply_overdue_scan_no_window_keeps_status() -> None:
    assert apply_overdue_scan(PARTIAL, current_chapter=99, expected_close_to=None) == PARTIAL


def test_apply_overdue_scan_idempotent_on_overdue() -> None:
    assert apply_overdue_scan(OVERDUE, current_chapter=12, expected_close_to=10) == OVERDUE


# ---- 枚举值与 DB 文本一致 ----


def test_status_values_match_db_text() -> None:
    assert OPEN == "OPEN"
    assert PARTIAL == "PARTIAL"
    assert CLOSED == "CLOSED"
    assert OVERDUE == "OVERDUE"
    assert {s.value for s in ForeshadowStatus} == {"OPEN", "PARTIAL", "CLOSED", "OVERDUE"}
