"""Reproduction: lifecycle-enforcement.verify-lifecycle-enforcement.

Expected:
- all 16 valid transitions succeed
- invalid transitions return Failure (not exceptions)
- AgentRunner flow in agent.py goes through lifecycle-enforced transitions

Actual:
- This script reports any deviation with assertion details.
"""

from __future__ import annotations

import tempfile
from pathlib import Path
from unittest.mock import patch

import pytest
from returns.result import Failure, Success

from anima.core.lifecycle import LifecycleEvent, can_transition
from anima.core.schemas import AgentState, AgentStateData, InternalError, PersonaSpec
from anima.shell.agent_sync import run_agent_sync
from anima.shell.state import read_state, transition_state, write_state


VALID_TRANSITIONS: list[tuple[AgentState, LifecycleEvent, AgentState]] = [
    (AgentState.pending, LifecycleEvent.start, AgentState.running),
    (AgentState.running, LifecycleEvent.finish, AgentState.completed),
    (AgentState.running, LifecycleEvent.fail, AgentState.failed),
    (AgentState.running, LifecycleEvent.suspend, AgentState.suspended),
    (
        AgentState.running,
        LifecycleEvent.request_approval,
        AgentState.awaiting_approval,
    ),
    (AgentState.running, LifecycleEvent.timeout, AgentState.failed),
    (AgentState.running, LifecycleEvent.budget_exhausted, AgentState.failed),
    (AgentState.running, LifecycleEvent.max_turns, AgentState.failed),
    (AgentState.running, LifecycleEvent.crash, AgentState.failed),
    (AgentState.running, LifecycleEvent.kill, AgentState.killed),
    (AgentState.running, LifecycleEvent.auto_complete, AgentState.completed),
    (AgentState.suspended, LifecycleEvent.resume, AgentState.running),
    (AgentState.suspended, LifecycleEvent.kill, AgentState.killed),
    (AgentState.awaiting_approval, LifecycleEvent.approve, AgentState.running),
    (AgentState.awaiting_approval, LifecycleEvent.reject, AgentState.running),
    (AgentState.awaiting_approval, LifecycleEvent.kill, AgentState.killed),
]


@pytest.fixture
def isolated_state_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
    """Configure ANIMA_STATE to a test temp directory for this test."""
    state_root = tmp_path / ".anima"
    state_root.mkdir(parents=True, exist_ok=True)
    monkeypatch.setenv("ANIMA_STATE", str(state_root))
    return state_root


def _seed_agent(instance_id: str, state: AgentState) -> None:
    data = AgentStateData(
        instance_id=instance_id,
        persona_id="qa",
        state=state,
        trace_id=f"tr_{instance_id}",
    )
    write_result = write_state(instance_id, data)
    assert isinstance(write_result, Success), (
        f"Failed to seed state for {instance_id}: {write_result}"
    )


def _assert_all_16_valid_transitions_succeed() -> None:
    """All valid transitions must return Success with expected next state."""
    for idx, (current, event, expected) in enumerate(VALID_TRANSITIONS):
        instance_id = f"ag_valid_{idx}"
        _seed_agent(instance_id, current)

        result = transition_state(instance_id, event)
        assert isinstance(result, Success), (
            f"Valid transition should succeed: {current.value} --{event.value}--> "
            f"{expected.value}. Got: {result}"
        )

        new_state = result.unwrap().state
        assert new_state == expected, (
            f"Wrong next state for {current.value} --{event.value}--> ?. "
            f"Expected {expected.value}, got {new_state.value}"
        )


def _assert_invalid_transitions_return_failure_not_exception() -> None:
    """Every invalid transition returns Failure and never raises."""
    failures_checked = 0

    for current in AgentState:
        for event in LifecycleEvent:
            if can_transition(current, event):
                continue

            failures_checked += 1
            instance_id = f"ag_invalid_{current.value}_{event.value}"
            _seed_agent(instance_id, current)

            try:
                result = transition_state(instance_id, event)
            except Exception as exc:  # pragma: no cover - explicit regression signal
                raise AssertionError(
                    "Invalid transition raised exception instead of returning Failure: "
                    f"{current.value} --{event.value}--> ? ({type(exc).__name__}: {exc})"
                ) from exc

            assert isinstance(result, Failure), (
                f"Invalid transition must return Failure, not {type(result).__name__}: "
                f"{current.value} --{event.value}--> ? | result={result}"
            )

    assert failures_checked > 0, "No invalid transitions were checked"


def _assert_agent_runner_flow_uses_lifecycle_enforced_transitions() -> None:
    """AgentRunner flow must invoke lifecycle-enforced transition_state calls."""
    instance_id = "ag_runner_flow"
    _seed_agent(instance_id, AgentState.pending)

    persona = PersonaSpec(id="qa", description="A test persona.", prompt="QA", model="gpt-4")
    observed_events: list[LifecycleEvent] = []

    # Import here to avoid top-level circular test setup issues.
    from anima.shell import state as state_module

    real_transition = state_module.transition_state

    def recording_transition(
        call_instance_id: str, event: LifecycleEvent, *args, **kwargs
    ):
        observed_events.append(event)
        return real_transition(call_instance_id, event, *args, **kwargs)

    with patch("anima.shell.agent.transition_state", side_effect=recording_transition):
        with patch(
            "anima.shell.agent.create_agent",
            return_value=Failure(InternalError("forced create_agent failure")),
        ):
            exit_code = run_agent_sync(
                instance_id=instance_id,
                task="qa verify lifecycle flow",
                persona=persona,
                job=None,
            )

    assert exit_code != 0, "run_agent_sync should fail with forced create_agent failure"
    assert observed_events, "No lifecycle transition calls observed in AgentRunner flow"
    assert observed_events[0] == LifecycleEvent.start, (
        "AgentRunner should first transition pending->running via LifecycleEvent.start, "
        f"got {observed_events[0].value}"
    )
    assert len(observed_events) >= 2, (
        "AgentRunner flow should apply a terminal transition after start when "
        "create_agent fails. Observed events: "
        f"{[e.value for e in observed_events]}"
    )
    assert observed_events[-1] in {
        LifecycleEvent.fail,
        LifecycleEvent.crash,
        LifecycleEvent.kill,
    }, (
        "Expected a terminal lifecycle event after forced failure, got: "
        f"{observed_events[-1].value}"
    )

    state_result = read_state(instance_id)
    assert isinstance(state_result, Success), "Final state should remain readable"
    final_state = state_result.unwrap().state
    assert final_state in {AgentState.failed, AgentState.killed}, (
        "AgentRunner flow should finish in a terminal failure state after forced "
        f"create_agent error, got: {final_state.value}"
    )


def test_all_16_valid_transitions_succeed(isolated_state_dir: Path) -> None:
    _assert_all_16_valid_transitions_succeed()


def test_invalid_transitions_return_failure_not_exception(
    isolated_state_dir: Path,
) -> None:
    _assert_invalid_transitions_return_failure_not_exception()


def test_agent_runner_flow_uses_lifecycle_enforced_transitions(
    isolated_state_dir: Path,
) -> None:
    _assert_agent_runner_flow_uses_lifecycle_enforced_transitions()


def main() -> None:
    with tempfile.TemporaryDirectory(prefix="anima_lifecycle_verify_") as td:
        state_root = Path(td)
        # Isolate ANIMA_STATE for this run.
        import os

        previous_anima_state = os.environ.get("ANIMA_STATE")
        os.environ["ANIMA_STATE"] = str(state_root)

        try:
            _assert_all_16_valid_transitions_succeed()
            _assert_invalid_transitions_return_failure_not_exception()
            _assert_agent_runner_flow_uses_lifecycle_enforced_transitions()
        finally:
            if previous_anima_state is None:
                os.environ.pop("ANIMA_STATE", None)
            else:
                os.environ["ANIMA_STATE"] = previous_anima_state

    print("PASS: lifecycle enforcement verification complete")


if __name__ == "__main__":
    main()
