"""Tests for the StateMachine implementation."""

from enum import Enum

import pytest

from pytola_core.patterns.statemachine import InvalidTransitionError, StateConfig, StateMachine, StateNotFoundError


class TestState(str, Enum):
    """测试用状态枚举."""

    __test__ = False  # 告诉 pytest 不要收集这个类作为测试

    IDLE = "idle"
    RUNNING = "running"
    PAUSED = "paused"
    STOPPED = "stopped"


class TestStateMachine:
    """测试 StateMachine 类."""

    def test_initial_state(self) -> None:
        """测试初始状态设置."""
        sm = StateMachine(initial_state=TestState.IDLE)
        assert sm.current_state == "idle"

    def test_force_set_state(self) -> None:
        """测试强制设置状态."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.force_set_state(TestState.RUNNING)
        assert sm.current_state == "running"

    def test_register_state(self) -> None:
        """测试注册状态配置."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))
        assert sm.has_state(TestState.IDLE)
        assert sm.state_config is not None
        assert "start_btn" in sm.state_config.enables

    def test_register_states(self) -> None:
        """测试批量注册状态."""
        sm = StateMachine(initial_state=TestState.IDLE)
        states_config: dict[TestState, StateConfig] = {
            TestState.IDLE: StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}),
            TestState.RUNNING: StateConfig(
                enables={"pause_btn", "stop_btn"},
                transitions={"pause": TestState.PAUSED, "stop": TestState.STOPPED},
            ),
        }
        sm.register_states(states_config)  # type: ignore[arg-type]
        assert sm.has_state(TestState.IDLE)
        assert sm.has_state(TestState.RUNNING)

    def test_valid_transition(self) -> None:
        """测试有效的状态转换."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))
        sm.register_state(TestState.RUNNING, StateConfig(enables={"stop_btn"}, transitions={}))

        assert sm.transition("start") is True
        assert sm.current_state == "running"

    def test_invalid_transition(self) -> None:
        """测试无效的状态转换."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))

        # 尝试执行未定义的转换
        with pytest.raises(InvalidTransitionError) as exc_info:
            sm.transition("pause")

        assert exc_info.value.current_state == "idle"
        assert exc_info.value.attempted_action == "pause"

    def test_transition_to_unregistered_state(self) -> None:
        """测试转换到未注册的状态."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))
        # 未注册 RUNNING 状态

        with pytest.raises(StateNotFoundError) as exc_info:
            sm.transition("start")

        assert exc_info.value.current_state == "idle"
        assert exc_info.value.attempted_action == "start"

    def test_state_change_callback(self) -> None:
        """测试状态变更回调."""
        callback_calls = []

        def on_state_change(old_state: str, new_state: str) -> None:
            callback_calls.append((old_state, new_state))

        sm = StateMachine(initial_state=TestState.IDLE, on_state_change=on_state_change)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))
        sm.register_state(TestState.RUNNING, StateConfig(enables={"stop_btn"}, transitions={}))

        sm.transition("start")

        assert len(callback_calls) == 1
        assert callback_calls[0] == ("idle", "running")

    def test_can_transition(self) -> None:
        """测试检查是否可以转换."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))

        assert sm.can_transition("start") is True
        assert sm.can_transition("pause") is False

    def test_get_available_actions(self) -> None:
        """测试获取可用动作列表."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.register_state(
            TestState.IDLE,
            StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING, "reset": TestState.STOPPED}),
        )

        actions = sm.get_available_actions()
        assert set(actions) == {"start", "reset"}

    def test_transition_history(self) -> None:
        """测试状态转换历史记录."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))
        sm.register_state(TestState.RUNNING, StateConfig(enables={"stop_btn"}, transitions={"stop": TestState.STOPPED}))
        sm.register_state(TestState.STOPPED, StateConfig(enables={"start_btn"}, transitions={}))

        sm.transition("start")
        sm.transition("stop")

        history = sm.get_transition_history()
        assert len(history) == 2
        assert history[0] == ("idle", "start", "running")
        assert history[1] == ("running", "stop", "stopped")

    def test_transition_history_limit(self) -> None:
        """测试限制历史记录数量."""
        sm = StateMachine(initial_state="state_0")
        # 注册多个状态和转换
        for i in range(5):
            state_name = f"state_{i}"
            next_state = f"state_{i + 1}" if i < 4 else "state_0"
            sm.register_state(state_name, StateConfig(enables=set(), transitions={"next": next_state}))

        # 执行多次转换
        for _ in range(4):
            sm.transition("next")

        # 只获取最近 2 条记录
        recent_history = sm.get_transition_history(limit=2)
        assert len(recent_history) == 2
        # 验证是最近的 2 条记录
        assert recent_history[0][0] == "state_2"  # 从 state_2 开始
        assert recent_history[1][0] == "state_3"  # 到 state_3

    def test_clear_history(self) -> None:
        """测试清空历史记录."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))
        sm.register_state(TestState.RUNNING, StateConfig(enables={"stop_btn"}, transitions={}))

        sm.transition("start")
        sm.clear_history()

        assert len(sm.get_transition_history()) == 0

    def test_normalize_state_with_enum(self) -> None:
        """测试 Enum 状态的标准化."""
        sm = StateMachine(initial_state=TestState.IDLE)
        assert sm._normalize_state(TestState.RUNNING) == "running"
        assert sm._normalize_state("idle") == "idle"

    def test_repr(self) -> None:
        """测试字符串表示."""
        sm = StateMachine(initial_state=TestState.IDLE)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))

        repr_str = repr(sm)
        assert "StateMachine" in repr_str
        assert "current_state='idle'" in repr_str
        assert "'idle'" in repr_str

    def test_callback_exception_handling(self) -> None:
        """测试回调函数异常处理."""

        def faulty_callback(old_state: str, new_state: str) -> None:
            msg = "Callback error"
            raise ValueError(msg)

        sm = StateMachine(initial_state=TestState.IDLE, on_state_change=faulty_callback)
        sm.register_state(TestState.IDLE, StateConfig(enables={"start_btn"}, transitions={"start": TestState.RUNNING}))
        sm.register_state(TestState.RUNNING, StateConfig(enables={"stop_btn"}, transitions={}))

        # 回调异常不应影响状态转换
        assert sm.transition("start") is True
        assert sm.current_state == "running"
