"""
Neura Runtime CLI — Entry point and health server.

Commands:
  neura-runtime start     Start the Runtime (tunnel + health server)
  neura-runtime stop      Stop the Runtime
  neura-runtime status    Show Runtime status
  neura-runtime pair      Pair this device with Neura Cloud
  neura-runtime models    List available local models
  neura-runtime apps      List running Streamlit apps
  neura-runtime build-images   Build Docker sandbox images
"""
import argparse
import asyncio
import json
import logging
import os
import signal
import sys
from typing import Optional

from .config import RuntimeConfig
from .auth import start_pairing, is_paired
from .docker_executor import DockerExecutor
from .streamlit_manager import StreamlitManager
from .ollama_proxy import OllamaProxy
from .tunnel import RuntimeTunnel

logger = logging.getLogger("neura_runtime")

# PID file for process management
PID_FILE = os.path.expanduser("~/.neura/runtime.pid")


async def _run_health_server(config: RuntimeConfig, executor: DockerExecutor, ollama: OllamaProxy, streamlit: StreamlitManager):
    """
    HTTP health server on localhost:9700.

    Endpoints:
      GET /health — returns {"status": "ok", "version": "0.1.0"}
      GET /status — detailed status (docker, models, apps)
    """
    from http.server import HTTPServer, BaseHTTPRequestHandler
    import threading

    class HealthHandler(BaseHTTPRequestHandler):
        def do_GET(self):
            if self.path == "/health":
                body = json.dumps({
                    "status": "ok",
                    "version": "0.1.0",
                    "paired": is_paired(),
                })
                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(body.encode())

            elif self.path == "/status":
                body = json.dumps({
                    "status": "ok",
                    "version": "0.1.0",
                    "paired": is_paired(),
                    "models": ollama.get_models(),
                    "apps": streamlit.list_apps(),
                })
                self.send_response(200)
                self.send_header("Content-Type", "application/json")
                self.send_header("Access-Control-Allow-Origin", "*")
                self.end_headers()
                self.wfile.write(body.encode())

            else:
                self.send_response(404)
                self.end_headers()

        def log_message(self, format, *args):
            pass  # Suppress default logging

    server = HTTPServer(("127.0.0.1", config.health_port), HealthHandler)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    logger.info(f"Health server running on http://localhost:{config.health_port}/health")
    return server


async def cmd_start(config: RuntimeConfig):
    """Start the Neura Runtime."""
    print(f"  Neura Runtime v0.1.0")
    print(f"  Health: http://localhost:{config.health_port}/health")

    # Check Docker
    executor = DockerExecutor(config)
    docker_ok = await executor.check_docker()
    if docker_ok:
        print("  Docker: available")
    else:
        print("  Docker: not available (code execution disabled)")

    # Init components
    ollama = OllamaProxy(config)
    streamlit = StreamlitManager(config, executor)
    tunnel = RuntimeTunnel(config, executor, streamlit, ollama)

    # Discover models
    models = await ollama.discover()
    if models:
        print(f"  Local models: {len(models)} found")
        for m in models[:5]:
            print(f"    - {m['name']} ({m['provider']})")
    else:
        print("  Local models: none found")

    # Start health server
    health_server = await _run_health_server(config, executor, ollama, streamlit)

    # Write PID file
    os.makedirs(os.path.dirname(PID_FILE), exist_ok=True)
    with open(PID_FILE, "w") as f:
        f.write(str(os.getpid()))

    # Handle shutdown
    loop = asyncio.get_event_loop()

    def shutdown():
        print("\n  Shutting down...")
        asyncio.create_task(_shutdown(tunnel, executor, streamlit, health_server))

    for sig in (signal.SIGINT, signal.SIGTERM):
        loop.add_signal_handler(sig, shutdown)

    # Connect tunnel if paired
    if is_paired():
        print("  Tunnel: connecting to Neura Cloud...")
        await tunnel.connect()
    else:
        print("  Tunnel: not paired. Run `neura-runtime pair` to connect.")
        # Keep alive for health server
        while True:
            await asyncio.sleep(1)


async def _shutdown(tunnel, executor, streamlit, health_server):
    """Graceful shutdown."""
    await tunnel.disconnect()
    await streamlit.stop_all()
    await executor.destroy_all()
    health_server.shutdown()

    if os.path.exists(PID_FILE):
        os.unlink(PID_FILE)

    # Exit after cleanup
    asyncio.get_event_loop().stop()


def cmd_stop():
    """Stop the running Runtime."""
    if os.path.exists(PID_FILE):
        with open(PID_FILE) as f:
            pid = int(f.read().strip())
        try:
            os.kill(pid, signal.SIGTERM)
            print(f"  Stopped Runtime (pid {pid})")
            os.unlink(PID_FILE)
        except ProcessLookupError:
            print("  Runtime is not running")
            os.unlink(PID_FILE)
    else:
        print("  Runtime is not running")


def cmd_status(config: RuntimeConfig):
    """Show Runtime status."""
    import urllib.request

    running = False
    if os.path.exists(PID_FILE):
        with open(PID_FILE) as f:
            pid = int(f.read().strip())
        try:
            os.kill(pid, 0)  # Check if process exists
            running = True
        except ProcessLookupError:
            os.unlink(PID_FILE)

    if running:
        try:
            resp = urllib.request.urlopen(f"http://localhost:{config.health_port}/status", timeout=2)
            data = json.loads(resp.read())
            print(f"  Status: running (pid {pid})")
            print(f"  Paired: {data.get('paired', False)}")
            print(f"  Models: {len(data.get('models', []))}")
            print(f"  Apps:   {len(data.get('apps', []))}")
            for app in data.get("apps", []):
                print(f"    - {app['title']}: {app['url']}")
        except Exception:
            print(f"  Status: running (pid {pid}) but health server not responding")
    else:
        print("  Status: stopped")


async def cmd_pair(config: RuntimeConfig):
    """Pair this device with Neura Cloud."""
    if is_paired():
        print("  This device is already paired.")
        print("  Run with --force to re-pair.")
        return
    await start_pairing(config)


async def cmd_models(config: RuntimeConfig):
    """List available local models."""
    ollama = OllamaProxy(config)
    models = await ollama.discover()

    if not models:
        print("  No local models found.")
        print(f"  Checked: Ollama ({config.ollama_url}), LM Studio ({config.lm_studio_url})")
        return

    print(f"  Found {len(models)} local model(s):\n")
    for m in models:
        size_info = ""
        if m.get("parameter_size"):
            size_info = f" ({m['parameter_size']})"
        if m.get("quantization"):
            size_info += f" [{m['quantization']}]"
        print(f"  {m['name']}{size_info}  [{m['provider']}]")


async def cmd_build_images():
    """Build Docker sandbox images."""
    image_dir = os.path.join(os.path.dirname(__file__), "..", "..", "sandbox-images")
    if not os.path.exists(image_dir):
        # Try relative to package
        image_dir = os.path.join(os.path.dirname(__file__), "..", "sandbox-images")

    if not os.path.exists(image_dir):
        print("  Error: sandbox-images directory not found")
        return

    images = {
        "neura-sandbox-default": "Dockerfile.default",
        "neura-sandbox-ml": "Dockerfile.ml",
        "neura-sandbox-data": "Dockerfile.data",
        "neura-sandbox-scrape": "Dockerfile.scrape",
    }

    for image_name, dockerfile in images.items():
        dockerfile_path = os.path.join(image_dir, dockerfile)
        if not os.path.exists(dockerfile_path):
            print(f"  Skipping {image_name}: {dockerfile} not found")
            continue

        print(f"  Building {image_name}...", end="", flush=True)
        proc = await asyncio.create_subprocess_exec(
            "docker", "build", "-t", image_name, "-f", dockerfile_path, image_dir,
            stdout=asyncio.subprocess.DEVNULL,
            stderr=asyncio.subprocess.PIPE,
        )
        _, stderr = await proc.communicate()

        if proc.returncode == 0:
            print(" done")
        else:
            print(f" FAILED\n    {stderr.decode()[-200:]}")


def main():
    """CLI entry point."""
    parser = argparse.ArgumentParser(
        prog="neura-runtime",
        description="Neura Runtime — Local agent executor",
    )
    parser.add_argument(
        "command",
        choices=["start", "stop", "status", "pair", "models", "apps", "build-images"],
        help="Command to run",
    )
    parser.add_argument("-v", "--verbose", action="store_true", help="Verbose logging")

    args = parser.parse_args()

    # Setup logging
    level = logging.DEBUG if args.verbose else logging.INFO
    logging.basicConfig(
        level=level,
        format="  %(message)s",
    )

    config = RuntimeConfig.load()

    if args.command == "start":
        asyncio.run(cmd_start(config))
    elif args.command == "stop":
        cmd_stop()
    elif args.command == "status":
        cmd_status(config)
    elif args.command == "pair":
        asyncio.run(cmd_pair(config))
    elif args.command == "models":
        asyncio.run(cmd_models(config))
    elif args.command == "apps":
        # Quick status check for apps
        cmd_status(config)
    elif args.command == "build-images":
        asyncio.run(cmd_build_images())


if __name__ == "__main__":
    main()
