"""Shared detached subprocess launcher for child agents.

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

from __future__ import annotations

import os
import subprocess
import sys
import tempfile
from pathlib import Path

from returns.result import Failure, Result, Success

from anima.core.lifecycle import LifecycleEvent
from anima.core.schemas import InternalError, JobSpec, PersonaSpec
from anima.shell.state import (
    get_agent_dir,
    get_state_dir,
    transition_state,
    write_agent_pid,
)


# @shell_complexity: Subprocess environment setup requires conditional env var handling (ANIMA_STATE, ANIMA_TRACE_ID, OPENAI_BASE_URL)
def launch_agent_subprocess(
    *,
    instance_id: str,
    task: str,
    persona: PersonaSpec,
    job: JobSpec,
    trace_id: str | None = None,
) -> Result[int, InternalError]:
    """Launch detached ``agent_runner.py`` subprocess for an existing agent state.

    The caller is responsible for creating the pending child state first.
    On launch failure, this helper transitions the child state to ``failed``.

    Temp persona/job files are intentionally left for the child process to clean
    up after reading, preventing parent/child file-race cleanup bugs.
    """
    temp_persona_file: tempfile._TemporaryFileWrapper[str] | None = None
    temp_job_file: tempfile._TemporaryFileWrapper[str] | None = None

    try:
        temp_persona_file = tempfile.NamedTemporaryFile(
            mode="w", suffix=".json", delete=False
        )
        temp_persona_file.write(persona.model_dump_json())
        temp_persona_file.flush()
        temp_persona_file.close()

        temp_job_file = tempfile.NamedTemporaryFile(
            mode="w", suffix=".json", delete=False
        )
        temp_job_file.write(job.model_dump_json())
        temp_job_file.flush()
        temp_job_file.close()

        runner_path = str(Path(__file__).parent / "agent_runner.py")
        cmd = [
            sys.executable,
            runner_path,
            instance_id,
            task,
            "--persona-file",
            temp_persona_file.name,
            "--job-file",
            temp_job_file.name,
        ]

        llm_base_url = os.environ.get("OPENAI_BASE_URL")
        if llm_base_url:
            cmd.extend(["--llm-base-url", llm_base_url])

        env = os.environ.copy()
        if "ANIMA_STATE" not in env:
            env["ANIMA_STATE"] = str(get_state_dir())
        if trace_id:
            env["ANIMA_TRACE_ID"] = trace_id

        stderr_log_path = get_agent_dir(instance_id) / "stderr.log"
        stderr_log = open(stderr_log_path, "w")  # noqa: SIM115
        try:
            proc = subprocess.Popen(
                cmd,
                stdout=subprocess.DEVNULL,
                stderr=stderr_log,
                stdin=subprocess.DEVNULL,
                start_new_session=True,
                env=env,
            )
        finally:
            stderr_log.close()

        pid_result = write_agent_pid(instance_id, proc.pid)
        if isinstance(pid_result, Failure):
            transition_state(
                instance_id,
                LifecycleEvent.fail,
                error=f"Failed to persist child PID: {pid_result.failure()}",
            )
            return Failure(
                InternalError(
                    f"Failed to write child PID: {pid_result.failure()}",
                    {"instance_id": instance_id, "pid": proc.pid},
                )
            )

        return Success(proc.pid)

    except (OSError, subprocess.SubprocessError, RuntimeError, ValueError) as exc:
        transition_state(
            instance_id,
            LifecycleEvent.fail,
            error=f"Subprocess launch error ({type(exc).__name__}): {exc}",
        )
        return Failure(
            InternalError(
                f"Failed to spawn subprocess: {exc}",
                {"instance_id": instance_id},
            )
        )
