"""Tests for spawn daemon with ANIMA_STATE environment variable.

Spec source: spawn-result-persistence.spawn-result-persistence-gate-retest-fix-retest-fix
Verifies that spawn respects ANIMA_STATE for stderr.log placement.
"""

import json
import os
import subprocess
import sys
import time
from pathlib import Path


TERMINAL_STATES = {"completed", "failed", "killed"}


def _write_persona(path: Path, *, name: str, model: str = "test") -> None:
    path.write_text(
        json.dumps(
            {
                "spec_version": "0.1.0",
                "name": name,
                "prompt": "You are a test agent.",
                "model": model,
            }
        )
    )


def _spawn_and_parse(
    *,
    task: str,
    persona_file: Path,
    anima_state: Path,
    extra_env: dict[str, str] | None = None,
    model: str = "test",
) -> tuple[subprocess.CompletedProcess[str], dict[str, object]]:
    """Spawn daemon with custom ANIMA_STATE and parse output."""
    # Use a short symlink path to avoid Unix socket path length limits
    short_state = Path(f"/tmp/anima_state_{os.getpid()}_{time.monotonic_ns()}")
    short_state.symlink_to(anima_state, target_is_directory=True)

    env = os.environ.copy()
    env["ANIMA_STATE"] = str(short_state)
    env["HOME"] = str(anima_state.parent)
    if extra_env:
        env.update(extra_env)

    result = subprocess.run(
        [
            sys.executable,
            "-m",
            "anima",
            "spawn",
            task,
            "--persona-file",
            str(persona_file),
            "--model",
            model,
            "--json",
        ],
        capture_output=True,
        text=True,
        timeout=10,
        env=env,
        check=False,
    )
    assert result.returncode == 0, (
        f"spawn failed rc={result.returncode}\nstdout={result.stdout}\nstderr={result.stderr}"
    )
    try:
        payload = json.loads(result.stdout)
    except json.JSONDecodeError as exc:
        raise AssertionError(
            f"spawn output was not valid JSON:\n{result.stdout}\n{result.stderr}"
        ) from exc
    assert isinstance(payload, dict), f"Expected JSON object payload, got: {payload!r}"
    assert isinstance(payload.get("instance_id"), str) and payload["instance_id"], (
        f"Missing instance_id in spawn output: {payload!r}"
    )
    return result, payload


def _poll_terminal_state(
    state_file: Path, timeout_seconds: float = 10.0
) -> dict[str, object]:
    deadline = time.monotonic() + timeout_seconds
    last_state: dict[str, object] | None = None

    while time.monotonic() < deadline:
        if state_file.exists():
            loaded = json.loads(state_file.read_text())
            if isinstance(loaded, dict):
                last_state = loaded
                if loaded.get("state") in TERMINAL_STATES:
                    return loaded
        time.sleep(0.1)

    raise AssertionError(
        f"state.json did not reach terminal state within {timeout_seconds:.1f}s: "
        f"last={last_state!r}, file={state_file}"
    )


def test_spawn_respects_anima_state_env(tmp_path: Path) -> None:
    """Spawn daemon respects ANIMA_STATE environment variable for state directory.

    Issue: spawn_impl was using hardcoded ~/.anima/agents path for stderr.log
    instead of get_agent_dir() which respects ANIMA_STATE.

    This caused "No such file or directory" errors when ANIMA_STATE was set
    to a non-default directory:
    - state.json created in $ANIMA_STATE/agents/<id>/state.json (correct)
    - stderr.log attempted in ~/.anima/agents/<id>/stderr.log (wrong!)

    Fix: Use get_agent_dir(instance_id) for stderr.log path resolution.
    """
    # Use isolated state directory
    state_dir = tmp_path / "anima_state_custom"
    state_dir.mkdir()

    persona_file = tmp_path / "persona_custom_state.json"
    _write_persona(persona_file, name="custom-state-test")

    _, payload = _spawn_and_parse(
        task="Test ANIMA_STATE environment variable",
        persona_file=persona_file,
        anima_state=state_dir,
    )

    instance_id = str(payload["instance_id"])

    # Verify state file is in custom ANIMA_STATE location
    state_file = state_dir / "agents" / instance_id / "state.json"
    assert state_file.exists(), (
        f"State file should be in custom ANIMA_STATE location: {state_file}"
    )

    # Verify stderr.log exists in the same directory (not in ~/.anima)
    stderr_log = state_dir / "agents" / instance_id / "stderr.log"
    assert stderr_log.exists(), (
        f"stderr.log should be in custom ANIMA_STATE location: {stderr_log}"
    )

    # Verify daemon reaches completed state (not failed)
    terminal = _poll_terminal_state(state_file, timeout_seconds=10.0)

    assert terminal.get("state") == "completed", (
        f"Expected completed state, got {terminal.get('state')!r} with error={terminal.get('error')!r}"
    )

    assert terminal.get("error") is None, (
        f"Completed state should not have error field: {terminal.get('error')!r}"
    )


def test_spawn_stderr_log_in_correct_location(tmp_path: Path) -> None:
    """Spawn daemon writes stderr.log to ANIMA_STATE directory, not hardcoded ~/.anima.

    Regression test: Previous bug had spawn_impl use:
        os.path.join(os.path.expanduser("~"), ".anima", "agents", instance_id, "stderr.log")

    Should use:
        os.path.join(get_agent_dir(instance_id), "stderr.log")
    """
    state_dir = tmp_path / "anima_custom_home"
    state_dir.mkdir()

    persona_file = tmp_path / "persona_stderr_test.json"
    _write_persona(persona_file, name="stderr-location-test")

    _, payload = _spawn_and_parse(
        task="Verify stderr.log location",
        persona_file=persona_file,
        anima_state=state_dir,
    )

    instance_id = str(payload["instance_id"])

    # CRITICAL: stderr.log MUST be in ANIMA_STATE directory
    stderr_in_custom = state_dir / "agents" / instance_id / "stderr.log"
    assert stderr_in_custom.exists(), (
        f"stderr.log must exist in custom ANIMA_STATE: {stderr_in_custom}"
    )

    # Verify stderr.log is NOT in default ~/.anima location
    default_stderr = Path.home() / ".anima" / "agents" / instance_id / "stderr.log"
    assert not default_stderr.exists(), (
        f"stderr.log should NOT be in default ~/.anima location: {default_stderr}"
    )
