"""Synchronous agent execution wrapper.

Provides run_agent_sync() — the entry point for detached agent runner processes.
Extracted from agent.py to reduce file size.
"""

# @invar:allow shell_result: Synchronous wrapper using exit codes for subprocess
# @shell_complexity: Crash-path cleanup with state transition and auto-rm requires branching

from __future__ import annotations

import asyncio
import sys
from typing import TYPE_CHECKING, Any

from returns.result import Failure, Success

from anima.core.lifecycle import LifecycleEvent
from anima.core.schemas import (
    AgentResult,
    AgentState,
    CapabilityToken,
    CompletionPolicy,
    JobSpec,
    PersonaSpec,
)
from anima.shell.agent import AgentRunner
from anima.shell.state import (
    transition_state,
    write_result,
)

if TYPE_CHECKING:
    from anima.core.sandbox import SandboxPolicy


# @invar:allow shell_result: Internal crash-transition helper returns optional state sentinel
def _handle_failed_result(instance_id: str, failure_message: str) -> AgentState | None:
    """Transition failed runs to failed state without masking root failure."""
    print(f"Agent runner error: {failure_message}", file=sys.stderr)
    try:
        transition_state(instance_id, LifecycleEvent.crash, error=failure_message)
        return AgentState.failed
    except Exception as transition_err:
        print(
            f"Warning: Failed to transition state to failed: {transition_err}",
            file=sys.stderr,
        )
        return None


# @invar:allow shell_result: Internal crash-transition helper returns optional state sentinel
def _handle_exception(instance_id: str, error: Exception) -> AgentState | None:
    """Handle unhandled runner crashes and best-effort failure transition."""
    try:
        trans_result = transition_state(
            instance_id,
            LifecycleEvent.crash,
            error=f"Agent crash ({type(error).__name__}): {error}",
        )
        if isinstance(trans_result, Failure):
            print(
                f"Warning: Failed to transition state to failed: {trans_result.failure()}",
                file=sys.stderr,
            )
            return None
        return AgentState.failed
    except Exception as transition_err:
        print(
            f"Warning: Exception during state transition: {transition_err}",
            file=sys.stderr,
        )
        return None


def _cleanup_terminal_state(
    instance_id: str,
    auto_rm: bool,
    final_state: AgentState | None,
) -> None:
    """Delete state directory only for terminal states when auto-rm is enabled."""
    if not auto_rm or final_state is None:
        return

    from anima.core.lifecycle import is_terminal
    from anima.shell.state import delete_agent_state

    if not is_terminal(final_state):
        return

    delete_result = delete_agent_state(instance_id)
    if isinstance(delete_result, Failure):
        print(
            f"Warning: Failed to clean up agent state: {delete_result.failure()}",
            file=sys.stderr,
        )


# @invar:allow shell_result: Synchronous wrapper using exit codes for subprocess
# @shell_complexity: Crash-path cleanup with state transition and auto-rm requires branching
# @shell_orchestration: Synchronous entrypoint coordinates runner, state transitions, and auto-cleanup side effects
def run_agent_sync(
    instance_id: str,
    task: str,
    persona: PersonaSpec | None = None,
    job: JobSpec | None = None,
    completion_policy: CompletionPolicy = CompletionPolicy.explicit,
    max_turns: int | None = None,
    model_override: str | None = None,
    base_url: str | None = None,
    memory_provider_name: str | None = None,
    mcp_servers: list[Any] | None = None,
    capability_token: CapabilityToken | None = None,
    sandbox_policy: "SandboxPolicy | None" = None,
    auto_rm: bool = False,
) -> int:
    """Synchronous wrapper for AgentRunner.run().

    This is the entry point for the detached agent runner process.

    Args:
        instance_id: Unique identifier for this agent (directory key for state).
        task: The task string for the agent to execute.
        persona: Optional PersonaSpec.
        job: Optional JobSpec (for budget and constraints).
        completion_policy: How completion is triggered.
        max_turns: Maximum turns allowed (None = unlimited).
        model_override: Optional model override from CLI ``--model``.
        base_url: Optional provider base URL override for model construction.
        mcp_servers: Optional list of MCP server toolsets to attach.
        capability_token: Optional CapabilityToken loaded from --token-file.
        sandbox_policy: Optional sandbox policy for tool execution.
        auto_rm: Delete agent state on terminal completion (default: False).

    Returns:
        Exit code (0 for success, non-zero for failure).
    """
    runner = AgentRunner(
        instance_id,
        task,
        persona,
        job=job,
        completion_policy=completion_policy,
        max_turns=max_turns,
        model_override=model_override,
        base_url=base_url,
        memory_provider_name=memory_provider_name,
        mcp_servers=mcp_servers,
        capability_token=capability_token,
        sandbox_policy=sandbox_policy,
    )

    # Run agent with crash cleanup
    # Spec source: stale-state-detection.run-cleanup
    # Unhandled exceptions/abnormal exit must transition state to failed
    # without masking the original exception.
    exit_code = 1
    final_state: AgentState | None = None
    try:
        result = asyncio.run(runner.run())

        if isinstance(result, Success):
            agent_result: AgentResult = result.unwrap()
            final_state = agent_result.final_state
            # Persist result for spawn-completed agents (anima collect)
            # Spec source: spawn-result-persistence.auto-write-result
            write_result(instance_id, agent_result)
            # Return 0 for completed, non-zero for killed
            if agent_result.final_state == AgentState.completed:
                exit_code = 0
            else:
                exit_code = 130  # 128 + SIGINT
        else:
            final_state = _handle_failed_result(instance_id, str(result.failure()))
            exit_code = 1
    except Exception as e:
        final_state = _handle_exception(instance_id, e)
        exit_code = 1
    finally:
        # Spec source: auto-rm.rm-on-spawn
        # Delete state directory ONLY when agent reaches terminal state.
        # Terminal states: completed, failed, killed (per lifecycle.py)
        # Do NOT delete if state is still running (e.g., from timeout).
        _cleanup_terminal_state(instance_id, auto_rm, final_state)

    return exit_code


__all__ = ["run_agent_sync"]
