"""Reproduction: spec-compliance.verify-spawn.

Expected: ctl_spawn_subagent creates a child agent that appears in list output,
has distinct instance_id/agent_id while inheriting parent trace context, and
spawn failures do not leave orphan child state or mutate parent children.
Actual:   One or more of these guarantees may fail.
"""

from __future__ import annotations

import asyncio
import os
import tempfile
from dataclasses import dataclass
from pathlib import Path

from returns.result import Failure, Success

from anima.core.schemas import AgentState, AgentStateData, PersonaSpec
from anima.shell.ctl import ctl_spawn_subagent
from anima.shell.mcp_agent import AgentMCPServer
from anima.shell.state import list_agents, read_state, write_state


@dataclass
class FakeAgentRunner:
    """Minimal AgentRunnerProtocol implementation for integration tests.

    Seam-pure integration path: no MagicMock, real protocol compliance.
    """

    instance_id: str


def _write_persona(path: Path, spec: PersonaSpec) -> None:
    path.write_text(spec.model_dump_json())


async def _run_repro() -> None:
    failures: list[str] = []

    with tempfile.TemporaryDirectory(prefix="anima_repro_spawn_") as tmp:
        previous_anima_state = os.environ.get("ANIMA_STATE")
        state_dir = Path(tmp) / ".anima"
        state_dir.mkdir(parents=True, exist_ok=True)
        os.environ["ANIMA_STATE"] = str(state_dir)

        work_dir = Path(tmp) / "work"
        personas_dir = work_dir / "personas"
        personas_dir.mkdir(parents=True, exist_ok=True)

        _write_persona(
            personas_dir / "parent.json",
            PersonaSpec(
                id="parent",
                description="A test persona.",
                prompt="You are a parent agent.",
                model="claude-sonnet-4-20250514",
                can_spawn=True,
            ),
        )
        _write_persona(
            personas_dir / "helper.json",
            PersonaSpec(
                id="helper",
                description="A test persona.",
                prompt="You are a helper agent.",
                model="claude-sonnet-4-20250514",
            ),
        )

        original_cwd = Path.cwd()
        os.chdir(work_dir)
        try:
            parent = AgentStateData(
                instance_id="inst_parent",
                trace_id="tr_spawn_verify",
                persona_name="parent",
                persona_ref="personas/parent.json",
                state=AgentState.running,
                depth=0,
                max_depth=3,
            )
            write_state("inst_parent", parent)

            # Happy path: spawn succeeds and child appears in list.
            spawn_result = await ctl_spawn_subagent(
                "inst_parent", "personas/helper.json", "Subtask: verify spawn"
            )
            if isinstance(spawn_result, Failure):
                failures.append(
                    "spawn(happy): expected Success, got Failure "
                    f"{spawn_result.failure()!r}"
                )
            else:
                spawned_value = spawn_result.unwrap()

                agents_result = list_agents()
                if isinstance(agents_result, Failure):
                    failures.append(
                        f"spawn(happy): list_agents failed {agents_result.failure()!r}"
                    )
                else:
                    agents = agents_result.unwrap()
                    children = [a for a in agents if a.parent_id == "inst_parent"]
                    if len(children) != 1:
                        failures.append(
                            "spawn(happy): expected exactly one child for ag_parent, "
                            f"found {len(children)}"
                        )
                    else:
                        child = children[0]
                        if child.instance_id == "inst_parent":
                            failures.append(
                                "spawn(edge): child.instance_id must differ from parent"
                            )
                        if child.instance_id == parent.instance_id:
                            failures.append(
                                "spawn(edge): child.instance_id must differ from parent"
                            )
                        if child.trace_id != parent.trace_id:
                            failures.append(
                                "spawn(edge): child trace_id should inherit from parent"
                            )

                        # Verify child can be interacted with through agent MCP surface.
                        # Seam-pure integration: FakeAgentRunner implements AgentRunnerProtocol.
                        child_server = AgentMCPServer(
                            child.instance_id,
                            FakeAgentRunner(instance_id=child.instance_id),
                        )
                        interact_task = asyncio.create_task(
                            child_server._handle_interact("ping")
                        )
                        await asyncio.sleep(0.05)
                        pending_queue = child_server.get_pending_message()
                        if pending_queue is None:
                            failures.append(
                                "spawn(happy): child interact queue was not created"
                            )
                        else:
                            queued = await asyncio.wait_for(
                                pending_queue.get(), timeout=1.0
                            )
                            if queued != "ping":
                                failures.append(
                                    "spawn(happy): child interaction queued unexpected "
                                    f"message {queued!r}"
                                )
                        child_server.set_response("pong", turn_number=1)
                        interact_payload = await asyncio.wait_for(
                            interact_task, timeout=2.0
                        )
                        if interact_payload.get("response") != "pong":
                            failures.append(
                                "spawn(happy): child interaction response mismatch "
                                f"{interact_payload!r}"
                            )

                        parent_after_success = read_state("inst_parent")
                        if isinstance(parent_after_success, Failure):
                            failures.append(
                                "spawn(edge): failed to read parent after success "
                                f"{parent_after_success.failure()!r}"
                            )
                        else:
                            parent_state = parent_after_success.unwrap()
                            if (
                                child.instance_id not in parent_state.children
                                and spawned_value not in parent_state.children
                            ):
                                failures.append(
                                    "spawn(edge): parent children list missing child reference "
                                    f"for child.instance_id={child.instance_id!r} and returned={spawned_value!r}"
                                )

            # Error path: invalid child persona must fail and leave no orphan child.
            before_error_list = list_agents()
            before_ids: set[str] = set()
            if isinstance(before_error_list, Success):
                before_ids = {a.instance_id for a in before_error_list.unwrap()}

            parent_before_error = read_state("inst_parent")
            parent_children_before = []
            if isinstance(parent_before_error, Success):
                parent_children_before = list(parent_before_error.unwrap().children)

            bad_spawn_result = await ctl_spawn_subagent(
                "inst_parent", "personas/does-not-exist.json", "Subtask: should fail"
            )
            if not isinstance(bad_spawn_result, Failure):
                failures.append(
                    "spawn(error): expected Failure for missing persona, got "
                    f"{bad_spawn_result!r}"
                )

            after_error_list = list_agents()
            if isinstance(after_error_list, Failure):
                failures.append(
                    "spawn(error): list_agents failed after failed spawn "
                    f"{after_error_list.failure()!r}"
                )
            else:
                after_ids = {a.instance_id for a in after_error_list.unwrap()}
                if after_ids != before_ids:
                    failures.append(
                        "spawn(error): failed spawn changed agent inventory "
                        f"before={sorted(before_ids)!r} after={sorted(after_ids)!r}"
                    )

            parent_after_error = read_state("inst_parent")
            if isinstance(parent_after_error, Failure):
                failures.append(
                    "spawn(error): failed to read parent after failed spawn "
                    f"{parent_after_error.failure()!r}"
                )
            else:
                parent_children_after = parent_after_error.unwrap().children
                if parent_children_after != parent_children_before:
                    failures.append(
                        "spawn(error): failed spawn mutated parent children "
                        f"before={parent_children_before!r} after={parent_children_after!r}"
                    )
        finally:
            os.chdir(original_cwd)
            if previous_anima_state is None:
                os.environ.pop("ANIMA_STATE", None)
            else:
                os.environ["ANIMA_STATE"] = previous_anima_state

    assert not failures, "\n".join(failures)


def repro_issue_spec_compliance_verify_spawn() -> None:
    """Spawn semantics match spec expectations."""
    asyncio.run(_run_repro())


if __name__ == "__main__":
    repro_issue_spec_compliance_verify_spawn()
    print("PASS: spec-compliance.verify-spawn is fixed")
