"""Integration coverage for ctl.spawn subprocess launch wiring.

Spec source: vectl step spawn-correctness.fix-ctl-spawn-actually-launch-child-subprocess.
"""

from __future__ import annotations

import asyncio
import os
import time
from pathlib import Path

import pytest
from returns.result import Success

from anima.core.schemas import AgentState, AgentStateData, PersonaSpec
from anima.shell.ctl_spawn import ctl_spawn_subagent
from anima.shell.state import read_log, read_state, write_state


def _pid_is_alive(pid: int | None) -> bool:
    """Return whether pid currently exists.

    Spec source: plan.yaml alignment-dead-code.fix-should-fix-flaky-spawn-test-cold-start
    failure-path observability requirement.
    """
    if pid is None:
        return False
    try:
        os.kill(pid, 0)
    except ProcessLookupError:
        return False
    except PermissionError:
        return True
    return True


def _format_readiness_diagnostics(
    *,
    child_id: str,
    state_root: Path,
    snapshots: list[str],
    log_events: list[str],
) -> str:
    """Format actionable diagnostics when pending->running is missed.

    Spec source: plan.yaml alignment-dead-code.fix-should-fix-flaky-spawn-test-cold-start
    requires readiness timeline and launch-failure observability.
    """
    stderr_path = state_root / "agents" / child_id / "stderr.log"
    if stderr_path.exists():
        stderr_lines = stderr_path.read_text().splitlines()
        stderr_tail = "\n".join(stderr_lines[-20:])
    else:
        stderr_tail = "<missing stderr.log>"

    timeline = "\n".join(snapshots[-12:]) if snapshots else "<no snapshots>"
    recent_events = "\n".join(log_events[-8:]) if log_events else "<no log events>"
    return (
        "readiness timeline (latest first):\n"
        f"{timeline}\n"
        "recent log events:\n"
        f"{recent_events}\n"
        f"stderr tail ({stderr_path}):\n{stderr_tail}"
    )


async def _wait_for_running_or_diagnose(
    child_id: str, state_root: Path, timeout_seconds: float
) -> None:
    """Wait for child running state with deterministic failure diagnostics.

    Spec source: plan.yaml alignment-dead-code.fix-should-fix-flaky-spawn-test-cold-start
    remediation: deterministic readiness observation without masking real failures.
    """
    deadline = time.monotonic() + timeout_seconds
    poll_interval = 0.05
    max_interval = 0.5
    fast_polls = 20
    polls_done = 0
    snapshots: list[str] = []
    log_events: list[str] = []
    pending_running_msg = f"Expected child {child_id} to transition pending->running within {timeout_seconds:.1f}s."

    while time.monotonic() < deadline:
        state_result = read_state(child_id)
        log_result = read_log(child_id)

        state_value = "<missing>"
        pid_value: int | None = None
        error_value = ""
        if isinstance(state_result, Success):
            child_state = state_result.unwrap()
            state_value = child_state.state.value
            pid_value = child_state.pid
            error_value = child_state.error or ""

        if isinstance(log_result, Success):
            events = log_result.unwrap()
            log_events = [
                f"{entry.get('event')} details={entry.get('details')!r}"
                for entry in events
            ]
            saw_start = any(entry.get("event") == "start" for entry in events)
        else:
            saw_start = False

        alive = _pid_is_alive(pid_value)
        elapsed = timeout_seconds - max(0.0, deadline - time.monotonic())
        snapshots.append(
            "t+"
            f"{elapsed:.2f}s state={state_value} pid={pid_value} "
            f"pid_alive={alive} error={error_value!r}"
        )

        if saw_start or state_value == AgentState.running.value:
            return

        if state_value in {
            AgentState.failed.value,
            AgentState.killed.value,
            AgentState.completed.value,
        }:
            diagnostics = _format_readiness_diagnostics(
                child_id=child_id,
                state_root=state_root,
                snapshots=snapshots,
                log_events=log_events,
            )
            raise AssertionError(
                f"{pending_running_msg} "
                f"Observed terminal state={state_value!r} before running.\n{diagnostics}"
            )

        await asyncio.sleep(poll_interval)
        polls_done += 1
        if polls_done >= fast_polls:
            poll_interval = min(poll_interval * 1.5, max_interval)

    diagnostics = _format_readiness_diagnostics(
        child_id=child_id,
        state_root=state_root,
        snapshots=snapshots,
        log_events=log_events,
    )
    raise AssertionError(
        f"{pending_running_msg} "
        "This may indicate the subprocess failed to start or readiness observation was missed.\n"
        f"{diagnostics}"
    )


@pytest.mark.asyncio
async def test_ctl_spawn_launches_child_to_running_within_five_seconds(
    tmp_state_dir: Path,
    tmp_path: Path,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    """ctl.spawn launches a child subprocess that reaches running quickly.

    This test verifies that spawned child agents transition to running state
    within a bounded timeout. Uses explicit log event detection as the primary
    indicator because 'start' event is written synchronously during the state
    transition (see state.py transition_state() which logs immediately after
    the state file is written).

    The polling strategy uses exponential backoff with an initial burst:
    - First 10 checks at 50ms interval (fast polling for quick startup)
    - Then exponential backoff up to 500ms (reduces CPU under load)
    - Total timeout: 5 seconds
    """
    work_dir = tmp_path / "work"
    personas_dir = work_dir / "personas"
    personas_dir.mkdir(parents=True, exist_ok=True)
    monkeypatch.chdir(work_dir)

    parent_persona = PersonaSpec(
        id="parent",
        description="A test persona.",
        prompt="You are a parent agent.",
        model="test",
        can_spawn=True,
    )
    child_persona = PersonaSpec(
        id="helper",
        description="A test persona.",
        prompt="You are a helper agent.",
        model="test",
    )
    (personas_dir / "parent.json").write_text(parent_persona.model_dump_json())
    (personas_dir / "helper.json").write_text(child_persona.model_dump_json())

    parent_state = AgentStateData(
        instance_id="inst_ctl_spawn_parent",
        trace_id="tr_ctl_spawn_parent",
        persona_id="parent",
        persona_ref="personas/parent.json",
        state=AgentState.running,
        depth=0,
        max_depth=4,
    )
    write_state(parent_state.instance_id, parent_state, "created")

    spawn_result = await ctl_spawn_subagent(
        parent_state.instance_id,
        "personas/helper.json",
        "Subtask: integration launch probe",
    )
    assert isinstance(spawn_result, Success)
    child_id = spawn_result.unwrap()

    # Spec source: plan.yaml alignment-dead-code.fix-should-fix-flaky-spawn-test-cold-start
    # cold-start remediation requests deterministic readiness + actionable diagnostics.
    await _wait_for_running_or_diagnose(
        child_id=child_id,
        state_root=tmp_state_dir,
        timeout_seconds=12.0,
    )
