"""Kill command implementation.

Spec source: plan step ``cli-refactor.cli-refactor-verify-cli-refactor-fix``
extracts kill command from cli.py without behavior changes.
"""

# @invar:allow function_size: CLI command implementations with process management
# @invar:allow shell_result: kill command exits with explicit status codes for CLI ergonomics

from __future__ import annotations

import sys

from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from returns.result import Failure

from anima.app.control_service import KillRequest
from anima.core.lifecycle import killable_states
from anima.core.schemas import AgentState, AgentStateData
from anima.shell.cli_commands._shared import (
    resolve_agent,
    shortest_unique_agent_id_prefixes as _shortest_unique_agent_id_prefixes,
)
from anima.shell.agent_kill import kill_agent_tree
from anima.shell.control_service_adapter import build_shell_control_service
from anima.shell.state import list_agents

EXIT_AGENT_NOT_FOUND = 2


# @shell_complexity: Single agent kill with state validation and process signaling
# @invar:allow function_size: CLI command with process management
def kill_impl(
    agent_id: str | None,
    kill_all: bool,
    states: tuple[str, ...],
    yes: bool,
    json_output: bool,
    console: Console,
) -> None:
    """Implementation for anima kill command.

    Sends SIGTERM to the agent process if running. Supports ID prefix matching.
    Validates that the agent is in a killable state before transition.

    Single agent mode (default):
        anima kill <agent_id>              Kill specific agent
        anima kill ag_abc123              Kill agent with prefix match

    Bulk mode (--all):
        anima kill --all                   Kill all killable agents (prompts for confirmation)
        anima kill --all --yes             Kill all killable agents (skip confirmation)
        anima kill --all --state running   Kill only running agents
        anima kill --all --state running --state suspended  Kill running and suspended

    Killable states: running, suspended, awaiting_approval.

    Exit codes: 0 success, 1 error (no agents, wrong state), 2 agent not found.
    """
    control_service = build_shell_control_service()

    killable = killable_states()
    terminal_states = {AgentState.completed, AgentState.failed, AgentState.killed}

    # --- Mode selection: single agent vs bulk ---
    if kill_all:
        # Bulk mode: --all flag
        return _kill_bulk(
            states=states,
            yes=yes,
            json_output=json_output,
            killable=killable,
            console=console,
        )

    # Single agent mode: agent_id required
    if agent_id is None:
        if json_output:
            console.print_json(
                data={
                    "error": {
                        "code": "INVALID_INPUT",
                        "numeric_code": 1,
                        "message": "agent_id is required when not using --all",
                    }
                }
            )
        else:
            console.print("[red]Error: agent_id is required when not using --all[/red]")
            console.print("[dim]Usage: anima kill <agent_id>[/dim]")
            console.print(
                "[dim]       anima kill --all [--yes] [--state <state>][/dim]"
            )
        sys.exit(1)

    # --- Single agent kill ---
    # 1. Resolve ID (prefix match)
    state = resolve_agent(
        agent_id,
        json_output=json_output,
        console=console,
        exit_agent_not_found=EXIT_AGENT_NOT_FOUND,
    )
    instance_id = state.instance_id

    # 2. Validate killable state
    if state.state in terminal_states:
        # Already terminal: clear message + exit 1
        if json_output:
            console.print_json(
                data={
                    "error": {
                        "code": "AGENT_TERMINAL",
                        "numeric_code": 313,
                        "message": f"Agent already in terminal state: {state.state.value}",
                        "details": {
                            "agent_id": instance_id,
                            "state": state.state.value,
                        },
                    }
                }
            )
        else:
            console.print(
                f"[yellow]Agent {instance_id} is already in terminal state: {state.state.value}[/yellow]"
            )
        sys.exit(1)

    if state.state not in killable:
        # Not killable (shouldn't happen if not terminal, but defensive)
        if json_output:
            console.print_json(
                data={
                    "error": {
                        "code": "INVALID_STATE",
                        "numeric_code": 1,
                        "message": f"Agent cannot be killed from state: {state.state.value}",
                        "details": {
                            "agent_id": instance_id,
                            "state": state.state.value,
                        },
                    }
                }
            )
        else:
            console.print(
                f"[red]Error: Agent cannot be killed from state: {state.state.value}[/red]"
            )
        sys.exit(1)

    # 3. Kill subtree rooted at target (children first, then target)
    kill_result = control_service.kill(
        KillRequest(instance_id=instance_id, reason="Killed by user")
    )
    if isinstance(kill_result, Failure):
        if json_output:
            console.print_json(
                data={
                    "error": {
                        "code": "INTERNAL",
                        "numeric_code": 10,
                        "message": f"Failed to kill agent tree: {kill_result.failure()}",
                        "details": {"agent_id": instance_id},
                    }
                }
            )
        else:
            console.print(
                f"[red]Error: Failed to kill agent tree: {kill_result.failure()}[/red]"
            )
        sys.exit(1)

    # 4. Output (result already written by transition_state for terminal state)
    if json_output:
        console.print_json(
            data={
                "instance_id": instance_id,
                "state": AgentState.killed.value,
            }
        )
    else:
        panel = Panel(
            f"[bold]Instance:[/] {instance_id}\n"
            f"[bold]State:[/] [yellow]killed[/yellow]",
            title="anima kill",
            border_style="yellow",
        )
        console.print(panel)

    sys.exit(0)


# @invar:allow function_size: Bulk kill with confirmation dialog
# @shell_complexity: Bulk kill with state filtering, confirmation, and multi-agent SIGTERM
def _kill_bulk(
    states: tuple[str, ...],
    yes: bool,
    json_output: bool,
    killable: frozenset[AgentState],
    console: Console,
) -> None:
    """Bulk kill agents in killable states.

    Args:
        states: User-specified state filters (empty = all killable states).
        yes: Skip confirmation prompt if True.
        json_output: Output in JSON format.
        killable: Set of killable states from lifecycle.
        console: Rich console for output.
    """
    # Parse --state filters (if provided)
    filter_states: list[AgentState] = []
    if states:
        for state_str in states:
            try:
                filter_states.append(AgentState(state_str))
            except ValueError:
                console.print(f"[red]Error: Invalid state '{state_str}'[/red]")
                sys.exit(1)
    else:
        # Default: all killable states
        filter_states = list(killable)

    # Validate filter_states are killable
    for fs in filter_states:
        if fs not in killable:
            console.print(
                f"[red]Error: Cannot kill agents in non-killable state '{fs.value}'[/red]"
            )
            console.print(
                "[dim]Killable states: running, suspended, awaiting_approval[/dim]"
            )
            sys.exit(1)

    # List all agents
    all_agents_result = list_agents()

    if isinstance(all_agents_result, Failure):
        if json_output:
            console.print_json(
                data={
                    "error": {
                        "code": "INTERNAL",
                        "numeric_code": 10,
                        "message": f"Failed to list agents: {all_agents_result.failure()}",
                    }
                }
            )
        else:
            console.print(
                f"[red]Error: Failed to list agents: {all_agents_result.failure()}[/red]"
            )
        sys.exit(1)

    all_agents = all_agents_result.unwrap()

    # Filter agents: must be in killable state AND match user's state filter
    agents_to_kill: list[AgentStateData] = []
    for agent in all_agents:
        if agent.state in filter_states:
            agents_to_kill.append(agent)

    # Sort by agent_id for consistent ordering
    agents_to_kill.sort(key=lambda a: a.agent_id)

    # Handle empty result
    if not agents_to_kill:
        if json_output:
            console.print_json(data={"killed": 0, "agents": []})
        else:
            if filter_states:
                state_names = ", ".join(s.value for s in filter_states)
                console.print(f"[dim]No agents found in states: {state_names}[/dim]")
            else:
                console.print("[dim]No killable agents found.[/dim]")
        sys.exit(0)

    # Show confirmation prompt (unless --yes)
    if not yes:
        if json_output:
            # JSON mode doesn't show confirmation - require --yes
            console.print_json(
                data={
                    "error": {
                        "code": "CONFIRMATION_REQUIRED",
                        "numeric_code": 1,
                        "message": "Confirmation required for --all. Use --yes to skip.",
                        "details": {"count": len(agents_to_kill)},
                    }
                }
            )
            sys.exit(1)

        # Human mode: show table and prompt
        table = Table(title=f"Agents to kill ({len(agents_to_kill)} total)")
        table.add_column("INSTANCE_ID", style="cyan")
        table.add_column("STATE", style="white")
        table.add_column("PERSONA", style="white")

        # State color mapping
        state_colors = {
            AgentState.running: "green",
            AgentState.suspended: "cyan",
            AgentState.awaiting_approval: "magenta",
        }

        display_id_map = _shortest_unique_agent_id_prefixes(
            [agent.instance_id for agent in agents_to_kill]
        )

        for agent in agents_to_kill:
            instance_id_short = display_id_map[agent.instance_id]
            state_color = state_colors.get(agent.state, "white")
            state_str = f"[{state_color}]{agent.state.value}[/{state_color}]"
            persona = agent.persona_name[:20] if agent.persona_name else "N/A"
            table.add_row(instance_id_short, state_str, persona)

        console.print(table)
        console.print(
            f"\n[yellow]About to kill {len(agents_to_kill)} agent(s).[/yellow]"
        )

        # Prompt for confirmation
        try:
            response = console.input("[bold]Confirm? (y/N):[/] ").strip().lower()
            if response != "y":
                console.print("[dim]Cancelled.[/dim]")
                sys.exit(0)
        except (EOFError, KeyboardInterrupt):
            console.print("\n[dim]Cancelled.[/dim]")
            sys.exit(1)

    # Perform the kills
    killed_count = 0
    killed_agents: list[AgentStateData] = []
    errors: list[tuple[str, str]] = []

    for agent in agents_to_kill:
        # Use kill_agent_tree for cascade kill (handles children + signals)
        tree_result = kill_agent_tree(agent.instance_id, error="Killed by user (bulk)")

        if isinstance(tree_result, Failure):
            errors.append((agent.instance_id, str(tree_result.failure())))
            continue

        # Count only if this agent was newly killed (not already killed by a
        # parent's cascade in a prior iteration)
        killed_ids = {s.instance_id for s in tree_result.unwrap()}
        if agent.instance_id in killed_ids:
            killed_count += 1
            killed_agents.append(agent)

    # Output results
    if json_output:
        output = {
            "killed": killed_count,
            "failed": len(errors),
            "agents": [
                {
                    "instance_id": a.instance_id,
                    "agent_id": a.instance_id,
                    "state": AgentState.killed.value,
                }
                for a in killed_agents
            ],
        }
        if errors:
            output["errors"] = [
                {"agent_id": aid, "instance_id": aid, "error": err}
                for aid, err in errors
            ]
        console.print_json(data=output)
    else:
        if killed_count > 0:
            console.print(f"[green]Killed {killed_count} agent(s)[/green]")

        if errors:
            console.print(f"[red]Failed to kill {len(errors)} agent(s):[/red]")
            display_id_map = _shortest_unique_agent_id_prefixes(
                [instance_id for instance_id, _ in errors]
            )
            for instance_id, err in errors[:5]:  # Show first 5 errors
                console.print(f"  [dim]{display_id_map[instance_id]}:[/] {err}")
            if len(errors) > 5:
                console.print(f"  [dim]... and {len(errors) - 5} more[/dim]")

    sys.exit(0)
