#!/usr/bin/env python3
"""
Synth AI CLI - Command line interface for Synth AI services.
"""

import importlib
import logging
import os
import shutil
import signal
import subprocess
import sys
import time

import click
import requests
from requests.exceptions import ConnectionError as RequestsConnectionError
from requests.exceptions import HTTPError

logger = logging.getLogger(__name__)


def find_sqld_binary() -> str | None:
    """Find the sqld binary in common locations."""
    # Check if sqld is in PATH
    sqld_path = shutil.which("sqld")
    if sqld_path:
        return sqld_path

    # Check common installation locations
    common_paths = [
        "/usr/local/bin/sqld",
        "/usr/bin/sqld",
        os.path.expanduser("~/.local/bin/sqld"),
        os.path.expanduser("~/bin/sqld"),
        # Package-specific location
        os.path.join(os.path.dirname(__file__), "bin", "sqld"),
    ]

    for path in common_paths:
        if os.path.exists(path) and os.access(path, os.X_OK):
            return path

    return None


def install_sqld():
    """Install sqld binary if not found."""
    sqld_bin = find_sqld_binary()
    if sqld_bin:
        return sqld_bin

    click.echo("🔧 sqld not found. Installing...")

    # Try to use the bundled install script first
    package_install_script = os.path.join(os.path.dirname(__file__), "install_sqld.sh")

    try:
        if os.path.exists(package_install_script):
            # Use bundled script
            subprocess.run(["bash", package_install_script], check=True)
        else:
            # Create install script inline
            install_script = """#!/bin/bash
set -e

SQLD_VERSION="v0.26.2"
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)

# Map architecture names
case "$ARCH" in
    x86_64) ARCH="x86_64" ;;
    aarch64|arm64) ARCH="aarch64" ;;
    *) echo "Unsupported architecture: $ARCH"; exit 1 ;;
esac

# Construct download URL
URL="https://github.com/tursodatabase/libsql/releases/download/libsql-server-${SQLD_VERSION}/sqld-${OS}-${ARCH}.tar.xz"

echo "📥 Downloading sqld ${SQLD_VERSION} for ${OS}-${ARCH}..."

# Download and extract
TMP_DIR=$(mktemp -d)
cd "$TMP_DIR"
curl -L -o sqld.tar.xz "$URL"
tar -xf sqld.tar.xz

# Install to user's local bin
mkdir -p ~/.local/bin
mv sqld ~/.local/bin/
chmod +x ~/.local/bin/sqld

# Clean up
cd -
rm -rf "$TMP_DIR"

echo "✅ sqld installed to ~/.local/bin/sqld"
"""

            # Write and execute install script
            with open("/tmp/install_sqld.sh", "w") as f:
                f.write(install_script)

            subprocess.run(["bash", "/tmp/install_sqld.sh"], check=True)
            os.unlink("/tmp/install_sqld.sh")

        # Add ~/.local/bin to PATH if needed
        local_bin = os.path.expanduser("~/.local/bin")
        if local_bin not in os.environ.get("PATH", ""):
            os.environ["PATH"] = f"{local_bin}:{os.environ.get('PATH', '')}"

        return os.path.expanduser("~/.local/bin/sqld")

    except Exception as e:
        click.echo(f"❌ Failed to install sqld: {e}", err=True)
        click.echo(
            "Please install sqld manually from: https://github.com/tursodatabase/libsql", err=True
        )
        sys.exit(1)


@click.group()
def cli():
    """Synth AI - Software for aiding the best and multiplying the will."""
    pass


@cli.group("env")
def env():
    """Environment management commands."""
    pass


@env.command("register")
@click.option("--name", required=True, help="Environment name (e.g., 'MyEnv-v1')")
@click.option(
    "--module", "module_path", required=True, help="Python module path (e.g., 'my_package.env')"
)
@click.option("--class-name", "class_name", required=True, help="Environment class name")
@click.option("--description", help="Optional description")
@click.option("--service-url", default="http://localhost:8901", help="Environment service URL")
def register_env(
    name: str, module_path: str, class_name: str, description: str | None, service_url: str
):
    """Register a new environment with the service."""
    payload = {
        "name": name,
        "module_path": module_path,
        "class_name": class_name,
    }
    if description:
        payload["description"] = description

    try:
        response = requests.post(f"{service_url}/registry/environments", json=payload, timeout=10)
        response.raise_for_status()

        result = response.json()
        click.echo(f"✅ {result['message']}")

    except RequestsConnectionError:
        click.echo(f"❌ Could not connect to environment service at {service_url}")
        click.echo("💡 Make sure the service is running: synth-ai serve")
    except HTTPError as e:
        error_detail = e.response.json().get("detail", str(e)) if e.response else str(e)
        click.echo(f"❌ Registration failed: {error_detail}")
    except Exception as e:
        click.echo(f"❌ Error: {e}")


@env.command("list")
@click.option("--service-url", default="http://localhost:8901", help="Environment service URL")
def list_envs(service_url: str):
    """List all registered environments."""
    try:
        response = requests.get(f"{service_url}/registry/environments", timeout=10)
        response.raise_for_status()

        result = response.json()
        environments = result["environments"]

        if not environments:
            click.echo("No environments registered.")
            return

        click.echo(f"\n📦 Registered Environments ({result['total_count']}):")
        click.echo("=" * 60)

        for env in environments:
            click.echo(f"🌍 {env['name']}")
            click.echo(f"   Class: {env['class_name']}")
            click.echo(f"   Module: {env['module']}")
            if env["description"]:
                click.echo(f"   Description: {env['description']}")
            click.echo()

    except RequestsConnectionError:
        click.echo(f"❌ Could not connect to environment service at {service_url}")
        click.echo("💡 Make sure the service is running: synth-ai serve")
    except Exception as e:
        click.echo(f"❌ Error: {e}")


@env.command("unregister")
@click.option("--name", required=True, help="Environment name to unregister")
@click.option("--service-url", default="http://localhost:8901", help="Environment service URL")
def unregister_env(name: str, service_url: str):
    """Unregister an environment from the service."""
    try:
        response = requests.delete(f"{service_url}/registry/environments/{name}", timeout=10)
        response.raise_for_status()

        result = response.json()
        click.echo(f"✅ {result['message']}")

    except RequestsConnectionError:
        click.echo(f"❌ Could not connect to environment service at {service_url}")
        click.echo("💡 Make sure the service is running: synth-ai serve")
    except HTTPError as e:
        if e.response.status_code == 404:
            click.echo(f"❌ Environment '{name}' not found in registry")
        else:
            error_detail = e.response.json().get("detail", str(e)) if e.response else str(e)
            click.echo(f"❌ Unregistration failed: {error_detail}")
    except Exception as e:
        click.echo(f"❌ Error: {e}")


@cli.command()
@click.option(
    "--url", default="sqlite+aiosqlite:///./synth_ai.db/dbs/default/data", help="Database URL"
)
def view(url: str):
    """Launch the interactive TUI dashboard."""
    try:
        module = importlib.import_module(".tui.dashboard", __package__)
        synth_dashboard_cls = getattr(module, "SynthDashboard")
        app = synth_dashboard_cls(db_url=url)
        app.run()
    except ImportError:
        click.echo("❌ Textual not installed. Install with: pip install textual", err=True)
        sys.exit(1)
    except KeyboardInterrupt:
        click.echo("\n👋 Dashboard closed", err=True)


# Note: subcommands (watch, experiments, experiment, usage, traces, status, recent, calc)
# are registered from the package module synth_ai.cli at import time.


@cli.command()
@click.option("--db-file", default="synth_ai.db", help="Database file path")
@click.option("--sqld-port", default=8080, type=int, help="Port for sqld HTTP interface")
@click.option("--env-port", default=8901, type=int, help="Port for environment service")
@click.option("--no-sqld", is_flag=True, help="Skip starting sqld daemon")
@click.option("--no-env", is_flag=True, help="Skip starting environment service")
@click.option(
    "--reload/--no-reload",
    default=False,
    help="Enable auto-reload (default: off). Or set SYNTH_RELOAD=1",
)
@click.option(
    "--force", is_flag=True, help="Kill any process already bound to --env-port without prompting"
)
def serve(
    db_file: str,
    sqld_port: int,
    env_port: int,
    no_sqld: bool,
    no_env: bool,
    reload: bool,
    force: bool,
):
    """Start Synth AI services (sqld daemon and environment service)."""

    # Configure logging
    logging.basicConfig(level=logging.INFO, format="%(message)s")

    processes = []

    def signal_handler(sig, frame):
        """Handle shutdown gracefully."""
        click.echo("\n🛑 Shutting down services...")
        for proc in processes:
            if proc.poll() is None:
                proc.terminate()
                try:
                    proc.wait(timeout=5)
                except subprocess.TimeoutExpired:
                    proc.kill()
        sys.exit(0)

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    # Start sqld if requested
    if not no_sqld:
        # Check if sqld is already running
        try:
            result = subprocess.run(
                ["pgrep", "-f", f"sqld.*--http-listen-addr.*:{sqld_port}"],
                capture_output=True,
                text=True,
            )
            if result.returncode == 0:
                click.echo(f"✅ sqld already running on port {sqld_port}")
                click.echo(f"   Database: {db_file}")
                click.echo(f"   HTTP API: http://127.0.0.1:{sqld_port}")
            else:
                # Find or install sqld
                sqld_bin = find_sqld_binary()
                if not sqld_bin:
                    sqld_bin = install_sqld()

                click.echo(f"🗄️  Starting sqld (local only) on port {sqld_port}")

                # Start sqld
                sqld_cmd = [
                    sqld_bin,
                    "--db-path",
                    db_file,
                    "--http-listen-addr",
                    f"127.0.0.1:{sqld_port}",
                ]

                # Create log file
                sqld_log = open("sqld.log", "w")  # noqa: SIM115
                proc = subprocess.Popen(sqld_cmd, stdout=sqld_log, stderr=subprocess.STDOUT)
                processes.append(proc)

                # Wait for sqld to start
                time.sleep(2)

                # Verify it started
                if proc.poll() is not None:
                    click.echo("❌ Failed to start sqld. Check sqld.log for details.", err=True)
                    with open("sqld.log") as f:
                        click.echo("\nLast 10 lines of sqld.log:")
                        lines = f.readlines()
                        for line in lines[-10:]:
                            click.echo(f"  {line.rstrip()}")
                    sys.exit(1)

                click.echo("✅ sqld started successfully!")
                click.echo(f"   Database: {db_file}")
                click.echo(f"   HTTP API: http://127.0.0.1:{sqld_port}")
                click.echo(f"   Log file: {os.path.abspath('sqld.log')}")

        except FileNotFoundError:
            click.echo("⚠️  pgrep not found, assuming sqld is not running")

    # Start environment service if requested
    if not no_env:
        click.echo("")
        click.echo(f"🚀 Starting Synth-AI Environment Service on port {env_port}")
        click.echo("")

        # Check if we're in a valid project directory
        if not os.path.exists("synth_ai") and not os.path.exists(
            os.path.join(os.path.dirname(__file__), "environments")
        ):
            click.echo("⚠️  Running from installed package, using package's environment service")
            # Use the installed module path
            env_module = "synth_ai.environments.service.app:app"
        else:
            # Running from source
            env_module = "synth_ai.environments.service.app:app"

        # Ensure env_port is free; offer to kill existing listeners
        try:
            import socket

            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                in_use = s.connect_ex(("127.0.0.1", env_port)) == 0
        except Exception:
            in_use = False

        if in_use:
            # Try to find PIDs using lsof (macOS/Linux)
            pids: list[str] = []
            try:
                out = subprocess.run(
                    ["lsof", "-ti", f":{env_port}"], capture_output=True, text=True
                )
                if out.returncode == 0 and out.stdout.strip():
                    pids = [p for p in out.stdout.strip().splitlines() if p]
            except FileNotFoundError:
                pids = []

            if force:
                if pids:
                    subprocess.run(["kill", "-9", *pids], check=False)
                    time.sleep(0.5)
            else:
                pid_info = f" PIDs: {', '.join(pids)}" if pids else ""
                if click.confirm(
                    f"⚠️  Port {env_port} is in use.{pid_info} Kill and continue?", default=True
                ):
                    if pids:
                        subprocess.run(["kill", "-9", *pids], check=False)
                        time.sleep(0.5)
                else:
                    click.echo(
                        "❌ Aborting. Re-run with --force to auto-kill or choose a different --env-port."
                    )
                    sys.exit(1)

        # Set environment variables
        env = os.environ.copy()
        env["SYNTH_LOGGING"] = "true"

        click.echo("📦 Environment:")
        click.echo(f"   Python: {sys.executable}")
        click.echo(f"   Working directory: {os.getcwd()}")
        click.echo("")
        click.echo("🔄 Starting services...")
        click.echo(f"   - sqld daemon: http://127.0.0.1:{sqld_port}")
        click.echo(f"   - Environment service: http://127.0.0.1:{env_port}")
        click.echo("")
        click.echo("💡 Tips:")
        click.echo("   - Check sqld.log if database issues occur")
        click.echo("   - Use Ctrl+C to stop all services")
        # Determine reload behavior: CLI flag overrides env var, default is off
        reload_enabled = reload or (os.getenv("SYNTH_RELOAD", "0") == "1")
        if reload_enabled:
            click.echo("   - Auto-reload ENABLED (code changes restart service)")
        else:
            click.echo("   - Auto-reload DISABLED (stable in-memory sessions)")
        click.echo("")

        # Start uvicorn
        uvicorn_cmd = [
            sys.executable,
            "-m",
            "uvicorn",
            env_module,
            "--host",
            "0.0.0.0",
            "--port",
            str(env_port),
            "--log-level",
            "info",
        ]

        if reload_enabled:
            uvicorn_cmd.append("--reload")
            # If running from source, add reload directory
            if os.path.exists("synth_ai"):
                uvicorn_cmd.extend(["--reload-dir", "synth_ai"])

        proc = subprocess.Popen(uvicorn_cmd, env=env)
        processes.append(proc)

    # Wait for processes
    if processes:
        click.echo("\n✨ All services started! Press Ctrl+C to stop.")
        try:
            for proc in processes:
                proc.wait()
        except KeyboardInterrupt:
            pass
    else:
        click.echo("No services to start.")


if __name__ == "__main__":
    cli()
