"""
Base Agent Runner — launches a child process with x402 payment capabilities.

Provides two injection strategies:
1. HTTP_PROXY env var (for HTTP traffic — transparent)
2. sitecustomize.py injection via PYTHONPATH (for Python processes — ag402.enable())

The runner manages the proxy lifecycle and child process lifecycle.

Note on injection strategy:
- PYTHONSTARTUP only works for interactive Python shells, NOT for scripts.
- We use sitecustomize.py + PYTHONPATH instead, which runs for ALL Python invocations
  (scripts, modules, interactive shells).
"""

from __future__ import annotations

import logging
import os
import shutil
import subprocess
import tempfile
import textwrap

logger = logging.getLogger(__name__)

_SITECUSTOMIZE_CODE = textwrap.dedent("""\
    # Auto-generated by Ag402 — enables x402 auto-payment for Python processes
    try:
        import ag402_core
        ag402_core.enable()
    except Exception:
        pass
""")


class AgentRunner:
    """Base runner that launches a command with x402 payment injection.

    Usage::

        runner = AgentRunner(["python", "my_agent.py"])
        await runner.run()
    """

    def __init__(
        self,
        command: list[str],
        *,
        use_proxy: bool = True,
        use_monkey_patch: bool = True,
        proxy_port: int = 14020,
    ):
        self.command = command
        self.use_proxy = use_proxy
        self.use_monkey_patch = use_monkey_patch
        self.proxy_port = proxy_port
        self._proxy = None
        self._tmpdir: str | None = None

    @property
    def label(self) -> str:
        """Human-readable label for the command."""
        return self.command[0] if self.command else "unknown"

    def validate(self) -> str | None:
        """Validate the command exists. Returns error message or None."""
        if not self.command:
            return "No command specified"
        if not shutil.which(self.command[0]):
            return f"Command not found: {self.command[0]}"
        return None

    async def run(self) -> int:
        """Run the command with payment injection. Returns exit code."""
        error = self.validate()
        if error:
            logger.error(error)
            return 1

        env = self._build_env()

        try:
            result = subprocess.run(self.command, env=env)
            return result.returncode
        except KeyboardInterrupt:
            return 130
        finally:
            self._cleanup_tmpdir()

    def _build_env(self) -> dict[str, str]:
        """Build environment variables for the child process."""
        env = os.environ.copy()
        env["AG402_ENABLED"] = "1"

        # Inject monkey-patch for Python processes via sitecustomize.py
        if self.use_monkey_patch and self._is_python_command():
            tmpdir = self._create_sitecustomize()
            # Prepend our tmpdir to PYTHONPATH so sitecustomize.py is picked up
            existing = env.get("PYTHONPATH", "")
            env["PYTHONPATH"] = f"{tmpdir}{os.pathsep}{existing}" if existing else tmpdir

        return env

    def _is_python_command(self) -> bool:
        """Check if the command is a Python interpreter."""
        if not self.command:
            return False
        base = os.path.basename(self.command[0]).lower()
        return base.startswith("python") or base in self._python_aliases()

    def _python_aliases(self) -> set[str]:
        """Known Python-based commands that should get the monkey-patch."""
        return {"uvicorn", "gunicorn", "flask", "django-admin"}

    def _create_sitecustomize(self) -> str:
        """Create a temporary directory with sitecustomize.py for injection."""
        self._tmpdir = tempfile.mkdtemp(prefix="ag402_")
        sc_path = os.path.join(self._tmpdir, "sitecustomize.py")
        with open(sc_path, "w") as f:
            f.write(_SITECUSTOMIZE_CODE)
        return self._tmpdir

    def _cleanup_tmpdir(self) -> None:
        """Clean up the temporary sitecustomize directory."""
        if self._tmpdir and os.path.exists(self._tmpdir):
            shutil.rmtree(self._tmpdir, ignore_errors=True)
            self._tmpdir = None
