"""Tests for error message quality in state transitions.

Spec source: spawn-result-persistence.spawn-result-persistence-gate-retest-fix-retest-fix-retest-fix
Purpose: Ensure error messages are descriptive, never raw str(e) for short exceptions.

Regression test for issue where:
- Daemon spawn failed with error: "a" (single character)
- error field contained uninformative single-char string
- talk attach blocked by terminal failed state

Root cause: Exception handlers used `error=str(e)` without wrapping,
allowing short exception messages (ValueError('a'), UserError('a'))
to propagate directly into state.json error field.

Fix: All transition_state(..., error=...) calls must format error as:
  error=f"... ({type(e).__name__}): {e}"
"""

from __future__ import annotations

from typing import Any

from returns.result import Success

from anima.core.lifecycle import LifecycleEvent
from anima.core.schemas import JobSpec, LifecycleType
from anima.shell.state import create_agent_state, read_state, transition_state


def test_transition_state_error_message_includes_type_name(
    tmp_state_dir: Any,
) -> None:
    """Error messages must include exception type name for clarity.

    When an exception with a short message (e.g., ValueError('a')) triggers
    a state transition, the error field should include context about what
    type of exception occurred, not just the raw message.

    This prevents uninformative error fields like error: "a".
    """
    _ = tmp_state_dir
    instance_id = "inst_error_quality_test"
    job = JobSpec(
        persona_ref="test@test",
        instance_id=instance_id,
        lifecycle=LifecycleType.ephemeral,
    )

    created = create_agent_state(instance_id, "test-persona", job)
    assert isinstance(created, Success)

    started = transition_state(instance_id, LifecycleEvent.start)
    assert isinstance(started, Success)

    # Simulate an exception with a very short message
    short_message_exception = ValueError("a")

    # Transition to failed with the short error
    # The fix ensures we include type name and context
    error_msg = f"Agent error ({type(short_message_exception).__name__}): {short_message_exception}"

    transition_result = transition_state(
        instance_id, LifecycleEvent.fail, error=error_msg
    )
    assert isinstance(transition_result, Success)

    # Verify error field has descriptive message, not just "a"
    state_result = read_state(instance_id)
    assert isinstance(state_result, Success)
    final_state = state_result.unwrap()

    # Error should contain type name and context
    assert final_state.error is not None
    assert len(final_state.error) > 10, (
        f"Error message too short: {final_state.error!r}"
    )
    assert "ValueError" in final_state.error, (
        f"Error should include exception type: {final_state.error!r}"
    )
    assert "Agent error" in final_state.error or "error" in final_state.error.lower(), (
        f"Error should include context: {final_state.error!r}"
    )


def test_error_field_preserves_full_exception_context(
    tmp_state_dir: Any,
) -> None:
    """Error messages should have useful context for debugging.

    Ensures that when errors are stored in state.json, they provide
    enough information for operators to diagnose issues quickly.
    """
    _ = tmp_state_dir
    instance_id = "inst_error_context_test"
    job = JobSpec(
        persona_ref="test@test",
        instance_id=instance_id,
        lifecycle=LifecycleType.ephemeral,
    )

    created = create_agent_state(instance_id, "test-persona", job)
    assert isinstance(created, Success)

    started = transition_state(instance_id, LifecycleEvent.start)
    assert isinstance(started, Success)

    # Test various error scenarios
    error_cases = [
        (
            OSError("Connection refused"),
            "OSError",
            "Connection refused",
        ),
        (
            ValueError("Invalid configuration"),
            "ValueError",
            "Invalid configuration",
        ),
        (
            RuntimeError("Unexpected state"),
            "RuntimeError",
            "Unexpected state",
        ),
    ]

    for exc, expected_type, expected_msg in error_cases:
        instance_id_test = f"inst_error_{expected_type}"
        created_test = create_agent_state(
            instance_id_test,
            "test-persona",
            job.model_copy(update={"instance_id": instance_id_test}),
        )
        assert isinstance(created_test, Success)

        started_test = transition_state(instance_id_test, LifecycleEvent.start)
        assert isinstance(started_test, Success)

        error_with_context = f"Test error ({type(exc).__name__}): {exc}"
        transition_result = transition_state(
            instance_id_test, LifecycleEvent.fail, error=error_with_context
        )
        assert isinstance(transition_result, Success)

        state_result = read_state(instance_id_test)
        final_state = state_result.unwrap()

        assert expected_type in final_state.error, (
            f"Error should include {expected_type}: {final_state.error!r}"
        )
        assert expected_msg in final_state.error, (
            f"Error should include '{expected_msg}': {final_state.error!r}"
        )
