"""Adapter to manage a vendored HexStrike MCP server.

This adapter provides a simple programmatic API to start/stop the vendored
HexStrike server (expected under ``third_party/hexstrike``) and to perform a
health check before returning control to the caller.

The adapter is intentionally lightweight (no Docker) and uses an async
subprocess so the server can run in the background while the TUI/runtime
operates.
"""

import asyncio
import os
import shutil
import sys
from pathlib import Path
from typing import Optional
import signal
import time

try:
    import aiohttp
except Exception:
    aiohttp = None


LOOT_DIR = Path("loot/artifacts")
LOOT_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE = LOOT_DIR / "hexstrike.log"


class HexstrikeAdapter:
    """Manage a vendored HexStrike server under `third_party/hexstrike`.

    Usage:
        adapter = HexstrikeAdapter()
        await adapter.start()
        # ... use MCPManager to connect to the server
        await adapter.stop()
    """

    def __init__(
        self,
        host: str = "127.0.0.1",
        port: int = 8888,
        python_cmd: str = "python3",
        server_script: Optional[Path] = None,
        cwd: Optional[Path] = None,
        env: Optional[dict] = None,
    ) -> None:
        self.host = host
        self.port = int(port)
        self.python_cmd = python_cmd
        self.server_script = (
            server_script
            or Path("third_party/hexstrike/hexstrike_server.py")
        )
        self.cwd = cwd or Path.cwd()
        self.env = {**os.environ, **(env or {})}

        self._process: Optional[asyncio.subprocess.Process] = None
        self._reader_task: Optional[asyncio.Task] = None

    def _build_command(self):
        return [self.python_cmd, str(self.server_script), "--port", str(self.port)]

    async def start(self, background: bool = True, timeout: int = 30) -> bool:
        """Start the vendored HexStrike server.

        Returns True if the server started and passed health check within
        `timeout` seconds.
        """
        if not self.server_script.exists():
            raise FileNotFoundError(
                f"HexStrike server script not found at {self.server_script}."
            )

        if self._process and self._process.returncode is None:
            return await self.health_check(timeout=1)

        cmd = self._build_command()

        # Resolve python command if possible
        resolved = shutil.which(self.python_cmd) or self.python_cmd

        self._process = await asyncio.create_subprocess_exec(
            resolved,
            *cmd[1:],
            cwd=str(self.cwd),
            env=self.env,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.STDOUT,
            start_new_session=True,
        )

        # Log PID for debugging and management
        try:
            pid = getattr(self._process, "pid", None)
            if pid:
                with LOG_FILE.open("a") as fh:
                    fh.write(f"[HexstrikeAdapter] started pid={pid}\n")
        except Exception:
            pass

        # Start a background reader task to capture logs
        loop = asyncio.get_running_loop()
        self._reader_task = loop.create_task(self._capture_output())

        # Wait for health check
        try:
            return await self.health_check(timeout=timeout)
        except Exception:
            return False

    async def _capture_output(self) -> None:
        """Capture stdout/stderr from the server and append to the log file."""
        if not self._process or not self._process.stdout:
            return

        try:
            with LOG_FILE.open("ab") as fh:
                while True:
                    line = await self._process.stdout.readline()
                    if not line:
                        break
                    # Prefix timestamps for easier debugging
                    fh.write(line)
                    fh.flush()
        except asyncio.CancelledError:
            pass
        except Exception:
            pass

    async def stop(self, timeout: int = 5) -> None:
        """Stop the server process gracefully."""
        proc = self._process
        if not proc:
            return

        try:
            proc.terminate()
            await asyncio.wait_for(proc.wait(), timeout=timeout)
        except asyncio.TimeoutError:
            try:
                proc.kill()
            except Exception:
                pass
        except Exception:
            pass

        self._process = None

        if self._reader_task and not self._reader_task.done():
            self._reader_task.cancel()
            try:
                await self._reader_task
            except Exception:
                pass

    def stop_sync(self, timeout: int = 5) -> None:
        """Synchronous stop helper for use during process-exit cleanup.

        This forcefully terminates the underlying subprocess PID if the
        async event loop is no longer available.
        """
        proc = self._process
        if not proc:
            return

        # Try to terminate gracefully first
        try:
            pid = getattr(proc, "pid", None)
            if pid:
                # Kill the whole process group if possible (handles children)
                try:
                    pgid = os.getpgid(pid)
                    os.killpg(pgid, signal.SIGTERM)
                except Exception:
                    try:
                        os.kill(pid, signal.SIGTERM)
                    except Exception:
                        pass

                # wait briefly for process to exit
                end = time.time() + float(timeout)
                while time.time() < end:
                    ret = getattr(proc, "returncode", None)
                    if ret is not None:
                        break
                    time.sleep(0.1)

                # If still running, force kill the process group
                try:
                    pgid = os.getpgid(pid)
                    os.killpg(pgid, signal.SIGKILL)
                except Exception:
                    try:
                        os.kill(pid, signal.SIGKILL)
                    except Exception:
                        pass
        except Exception:
            pass

    def __del__(self):
        try:
            self.stop_sync()
        except Exception:
            pass
        # Clear references
        try:
            self._process = None
        except Exception:
            pass

    async def health_check(self, timeout: int = 5) -> bool:
        """Check the server health endpoint. Returns True if healthy."""
        url = f"http://{self.host}:{self.port}/health"

        if aiohttp:
            try:
                async with aiohttp.ClientSession() as session:
                    async with session.get(url, timeout=timeout) as resp:
                        return resp.status == 200
            except Exception:
                return False

        # Fallback: synchronous urllib in thread
        import urllib.request

        def _check():
            try:
                with urllib.request.urlopen(url, timeout=timeout) as r:
                    return r.status == 200
            except Exception:
                return False

        loop = asyncio.get_running_loop()
        return await loop.run_in_executor(None, _check)

    def is_running(self) -> bool:
        return self._process is not None and self._process.returncode is None


__all__ = ["HexstrikeAdapter"]
