"""
KHY Deploy — one-click server deployment for KHY-OS.

Usage:
    khy deploy --domain ai.khyquant.top          # Full deploy
    khy deploy status                         # Show deployment status
    khy deploy nginx                          # Regenerate nginx config only
    khy deploy ssl                            # Renew/setup SSL cert only

Requires: Linux, root/sudo, Node.js >= 18.
Installs: nginx, certbot, python3-certbot-nginx.
Creates: systemd services + nginx reverse proxy + Let's Encrypt HTTPS.
"""
import getpass
import json
import os
import shutil
import subprocess
import sys
import textwrap
from datetime import datetime
from pathlib import Path

# ── Colors ──────────────────────────────────────────────────────────

_NO_COLOR = os.environ.get("NO_COLOR", "")

def _c(code: str, text: str) -> str:
    if _NO_COLOR or not sys.stdout.isatty():
        return text
    return f"\033[{code}m{text}\033[0m"

def _info(msg: str):
    print(f"{_c('36', '[INFO]')}  {msg}")

def _ok(msg: str):
    print(f"{_c('32', '[ OK ]')}  {msg}")

def _warn(msg: str):
    print(f"{_c('33', '[WARN]')}  {msg}")

def _fail(msg: str):
    print(f"{_c('31', '[FAIL]')}  {msg}", file=sys.stderr)

def _step(title: str):
    print(f"\n{_c('1', f'── {title} ──')}")

def _bold(text: str) -> str:
    return _c("1", text)

# ── Helpers ─────────────────────────────────────────────────────────

def _run(cmd: str, check: bool = True, capture: bool = False, **kw) -> subprocess.CompletedProcess:
    return subprocess.run(
        cmd, shell=True, check=check, capture_output=capture,
        text=True, **kw,
    )

def _has_cmd(name: str) -> bool:
    return shutil.which(name) is not None

def _is_root() -> bool:
    return os.geteuid() == 0

def _sudo(cmd: str, check: bool = True) -> subprocess.CompletedProcess:
    prefix = "" if _is_root() else "sudo "
    return _run(f"{prefix}{cmd}", check=check)

def _get_backend_dir() -> Path:
    """Locate the backend directory (same logic as cli.py)."""
    try:
        from khy_platform.cli import get_bundle_dir
        return get_bundle_dir()
    except Exception:
        # Fallback: check common locations (forest layout: services/backend,
        # platform/khy_platform/ -> platform -> repo root, bundle mirrors the tree)
        candidates = [
            Path.cwd() / "services" / "backend",
            Path(__file__).resolve().parent.parent.parent / "services" / "backend",
            Path(sys.prefix) / "lib" / "khy_os" / "bundled" / "services" / "backend",
        ]
        for p in candidates:
            if (p / "package.json").exists():
                return p
    _fail("Cannot locate KHY backend directory. Is khy-os installed?")
    sys.exit(1)

def _get_node() -> str:
    for name in ("node", "nodejs"):
        p = shutil.which(name)
        if p:
            return p
    _fail("Node.js not found. Install Node.js >= 18 first.")
    sys.exit(1)

def _detect_pkg_manager() -> str:
    """Detect apt/yum/dnf."""
    for pm in ("apt-get", "dnf", "yum"):
        if _has_cmd(pm):
            return pm
    return "apt-get"

# ── Nginx Config ────────────────────────────────────────────────────

NGINX_SITES_AVAILABLE = Path("/etc/nginx/sites-available")
NGINX_SITES_ENABLED = Path("/etc/nginx/sites-enabled")
NGINX_CONF_D = Path("/etc/nginx/conf.d")
WS_MAP_CONF = NGINX_CONF_D / "khy-ws-map.conf"

WS_MAP_CONTENT = textwrap.dedent("""\
    # KHY WebSocket upgrade map (managed by khy deploy)
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }
""")

def _generate_nginx_config(domain: str, backend_port: int = 3000,
                           frontend_port: int = 8080,
                           ai_backend_port: int = 9090,
                           ai_frontend_port: int = 8090,
                           bridge_port: int = 9222) -> str:
    return textwrap.dedent(f"""\
        # KHY-OS Full-Stack Reverse Proxy
        # Domain: {domain}
        # Generated: {datetime.now().isoformat()}
        # Managed by: khy deploy

        server {{
            listen 80;
            server_name {domain};

            # ── Quant Frontend (SPA) ──
            location / {{
                proxy_pass http://127.0.0.1:{frontend_port};
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
            }}

            # ── Backend API + WebSocket ──
            location /api/ {{
                proxy_pass http://127.0.0.1:{backend_port}/api/;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $connection_upgrade;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_read_timeout 86400s;
            }}

            # ── AI Management Panel ──
            location /ai/ {{
                proxy_pass http://127.0.0.1:{ai_frontend_port}/;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
            }}

            location /ai/api/ {{
                proxy_pass http://127.0.0.1:{ai_backend_port}/api/;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $connection_upgrade;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
                proxy_read_timeout 86400s;
            }}

            # ── Mobile Remote Control ──
            location /remote/ {{
                proxy_pass http://127.0.0.1:{bridge_port}/;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;
            }}

            location /remote/ws {{
                proxy_pass http://127.0.0.1:{bridge_port}/;
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection $connection_upgrade;
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_read_timeout 86400s;
                proxy_send_timeout 86400s;
            }}

            # ── Health Check ──
            location /health {{
                proxy_pass http://127.0.0.1:{backend_port}/health;
            }}

            # ── Performance ──
            gzip on;
            gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
            gzip_min_length 1024;
            gzip_vary on;

            client_max_body_size 50m;
        }}
    """)

# ── Systemd Services ───────────────────────────────────────────────

SYSTEMD_DIR = Path("/etc/systemd/system")

def _systemd_unit(name: str, description: str, exec_start: str,
                  working_dir: str, user: str, env_lines: list[str] | None = None) -> str:
    env_section = ""
    if env_lines:
        env_section = "\n".join(f"Environment={e}" for e in env_lines)
        env_section = f"\n{env_section}"

    return textwrap.dedent(f"""\
        [Unit]
        Description={description}
        After=network.target

        [Service]
        Type=simple
        User={user}
        WorkingDirectory={working_dir}
        ExecStart={exec_start}
        Restart=always
        RestartSec=5
        Environment=NODE_ENV=production{env_section}
        StandardOutput=journal
        StandardError=journal
        SyslogIdentifier={name}

        [Install]
        WantedBy=multi-user.target
    """)

SERVICE_DEFS = [
    {
        "name": "khy-backend",
        "description": "KHY Backend API Server",
        "script": "server.js",
        "base_dir_key": "backend",
        "env": ["PORT=3000"],
    },
    {
        "name": "khy-ai",
        "description": "KHY AI Management Backend",
        "script": "server.js",
        "base_dir_key": "ai-backend",
        "env": [],
    },
    {
        "name": "khy-bridge",
        "description": "KHY Mobile Bridge Server",
        "script": None,  # special inline script
        "base_dir_key": "backend",
        "env": ["BRIDGE_PORT=9222"],
    },
]

# ── Deployment Steps ────────────────────────────────────────────────

def _check_prerequisites():
    _step("Step 1: Prerequisites")

    if sys.platform != "linux":
        _fail("khy deploy is designed for Linux servers.")
        sys.exit(1)
    _ok("Linux detected")

    if not _is_root() and not _has_cmd("sudo"):
        _fail("Root or sudo access required.")
        sys.exit(1)
    _ok("Root/sudo access available")

    node = _get_node()
    result = _run(f"{node} -v", capture=True, check=False)
    if result.returncode == 0:
        ver = result.stdout.strip().lstrip("v")
        major = int(ver.split(".")[0])
        if major < 18:
            _fail(f"Node.js {ver} found, but >= 18 required.")
            sys.exit(1)
        _ok(f"Node.js {ver}")
    else:
        _fail("Cannot determine Node.js version.")
        sys.exit(1)

    return node

def _install_packages():
    _step("Step 2: System Packages")

    pm = _detect_pkg_manager()
    packages_needed = []

    if not _has_cmd("nginx"):
        packages_needed.append("nginx")
    else:
        _ok("nginx already installed")

    if not _has_cmd("certbot"):
        if pm == "apt-get":
            packages_needed.extend(["certbot", "python3-certbot-nginx"])
        else:
            packages_needed.extend(["certbot", "certbot-nginx"])
    else:
        _ok("certbot already installed")

    if packages_needed:
        _info(f"Installing: {', '.join(packages_needed)}")
        if pm == "apt-get":
            _sudo("apt-get update -qq")
            _sudo(f"apt-get install -y -qq {' '.join(packages_needed)}")
        elif pm == "dnf":
            _sudo(f"dnf install -y -q {' '.join(packages_needed)}")
        else:
            _sudo(f"yum install -y -q {' '.join(packages_needed)}")
        _ok("Packages installed")

    # Ensure nginx is enabled
    _sudo("systemctl enable nginx", check=False)
    _sudo("systemctl start nginx", check=False)

def _setup_backend(node: str) -> dict:
    """Locate backend dirs and ensure npm install is done."""
    _step("Step 3: Backend Setup")

    backend_dir = _get_backend_dir()
    _ok(f"Backend: {backend_dir}")

    # ai-backend is adjacent
    project_root = backend_dir.parent
    ai_backend_dir = project_root / "ai-backend"
    if not ai_backend_dir.exists():
        _warn(f"ai-backend not found at {ai_backend_dir}")
        ai_backend_dir = None
    else:
        _ok(f"AI Backend: {ai_backend_dir}")

    # Bootstrap (npm install + .env + DB seed) if needed
    if not (backend_dir / "node_modules").exists():
        _info("Running npm install in backend...")
        _run(f"cd {backend_dir} && npm install --no-audit --no-fund", check=False)

    if ai_backend_dir and not (ai_backend_dir / "node_modules").exists():
        # ai-backend symlinks to backend/node_modules, but check
        _info("Checking ai-backend dependencies...")
        nm = ai_backend_dir / "node_modules"
        if not nm.exists():
            _run(f"cd {ai_backend_dir} && npm install --no-audit --no-fund", check=False)

    _ok("Dependencies ready")
    return {
        "backend": str(backend_dir),
        "ai-backend": str(ai_backend_dir) if ai_backend_dir else str(backend_dir),
        "project_root": str(project_root),
    }

def _write_nginx_config(domain: str):
    _step("Step 4: Nginx Config")

    # WebSocket upgrade map
    NGINX_CONF_D.mkdir(parents=True, exist_ok=True)
    if not WS_MAP_CONF.exists():
        _sudo(f"tee {WS_MAP_CONF} > /dev/null << 'KHYEOF'\n{WS_MAP_CONTENT}KHYEOF")
        _ok(f"WebSocket map: {WS_MAP_CONF}")
    else:
        _ok(f"WebSocket map already exists")

    # Main site config
    config = _generate_nginx_config(domain)
    conf_name = "khy.conf"

    # Try sites-available first (Debian/Ubuntu), fall back to conf.d (RHEL/CentOS)
    if NGINX_SITES_AVAILABLE.exists():
        conf_path = NGINX_SITES_AVAILABLE / conf_name
        enabled_path = NGINX_SITES_ENABLED / conf_name

        _sudo(f"tee {conf_path} > /dev/null << 'KHYEOF'\n{config}KHYEOF")
        _ok(f"Config: {conf_path}")

        # Symlink to sites-enabled
        if not enabled_path.exists():
            _sudo(f"ln -sf {conf_path} {enabled_path}")
            _ok(f"Enabled: {enabled_path}")

        # Remove default if it conflicts
        default_enabled = NGINX_SITES_ENABLED / "default"
        if default_enabled.exists():
            _sudo(f"rm -f {default_enabled}")
            _info("Removed default nginx site")
    else:
        conf_path = NGINX_CONF_D / conf_name
        _sudo(f"tee {conf_path} > /dev/null << 'KHYEOF'\n{config}KHYEOF")
        _ok(f"Config: {conf_path}")

    # Test and reload
    result = _sudo("nginx -t", check=False)
    if result.returncode != 0:
        _fail("nginx -t failed! Check config manually.")
        return False

    _sudo("nginx -s reload", check=False)
    _ok("Nginx reloaded")
    return True

def _setup_ssl(domain: str, email: str | None = None):
    _step("Step 5: SSL Certificate")

    if not _has_cmd("certbot"):
        _warn("certbot not found, skipping SSL")
        return

    email_flag = f"--email {email}" if email else f"--email admin@{domain}"

    # Check if cert already exists
    cert_path = Path(f"/etc/letsencrypt/live/{domain}/fullchain.pem")
    if cert_path.exists():
        _ok(f"Certificate already exists for {domain}")
        # Try renewal
        result = _sudo("certbot renew --dry-run", check=False)
        if result.returncode == 0:
            _ok("Certificate renewal check passed")
        return

    _info(f"Requesting certificate for {domain}...")
    result = _sudo(
        f"certbot --nginx -d {domain} "
        f"--non-interactive --agree-tos {email_flag} "
        f"--redirect",
        check=False,
    )

    if result.returncode == 0:
        _ok(f"SSL certificate installed for {domain}")
    else:
        _warn("certbot failed — SSL not configured. "
              "Ensure DNS A record points to this server, then run: khy deploy ssl")

def _create_systemd_services(node: str, dirs: dict, user: str):
    _step("Step 6: Systemd Services")

    for svc in SERVICE_DEFS:
        name = svc["name"]
        svc_file = SYSTEMD_DIR / f"{name}.service"
        base_dir = dirs.get(svc["base_dir_key"], dirs["backend"])

        if svc["script"]:
            exec_start = f"{node} {svc['script']}"
        else:
            # Bridge: inline keep-alive script
            bridge_js = (
                "const b = require('./src/bridge/bridgeServer');"
                "b.startBridgeServer().then(r => {"
                "console.log('Bridge:', r.port ? 'http://0.0.0.0:' + r.port : 'failed');"
                "});"
                "setInterval(() => {}, 60000);"
            )
            exec_start = f'{node} -e "{bridge_js}"'

        unit = _systemd_unit(
            name=name,
            description=svc["description"],
            exec_start=exec_start,
            working_dir=base_dir,
            user=user,
            env_lines=svc.get("env"),
        )

        _sudo(f"tee {svc_file} > /dev/null << 'KHYEOF'\n{unit}KHYEOF")
        _ok(f"{svc_file}")

    # Reload and enable
    _sudo("systemctl daemon-reload")
    for svc in SERVICE_DEFS:
        name = svc["name"]
        _sudo(f"systemctl enable {name}", check=False)
        _sudo(f"systemctl restart {name}", check=False)
        _ok(f"{name}: enabled + started")

def _health_check(domain: str):
    _step("Step 7: Health Check")

    checks = [
        ("Backend", "http://127.0.0.1:3000/health"),
        ("Bridge", "http://127.0.0.1:9222/health"),
    ]

    for label, url in checks:
        result = _run(f"curl -sf {url}", check=False, capture=True)
        if result.returncode == 0:
            _ok(f"{label}: {url}")
        else:
            _warn(f"{label}: {url} (not responding yet — may need a moment)")

    # External check
    result = _run(f"curl -sf https://{domain}/health", check=False, capture=True)
    if result.returncode == 0:
        _ok(f"External: https://{domain}/health")
    else:
        result2 = _run(f"curl -sf http://{domain}/health", check=False, capture=True)
        if result2.returncode == 0:
            _ok(f"External: http://{domain}/health (HTTP only)")
        else:
            _warn(f"External health check failed — DNS may not be configured yet")

def _print_summary(domain: str):
    print()
    print(_bold("=" * 50))
    print(_bold("  KHY Deploy Complete"))
    print(_bold("=" * 50))
    print()
    print(f"  {_bold('Domain:')}     {domain}")
    print(f"  {_bold('Frontend:')}   https://{domain}/")
    print(f"  {_bold('AI Panel:')}   https://{domain}/ai/")
    print(f"  {_bold('Remote:')}     https://{domain}/remote/")
    print(f"  {_bold('API:')}        https://{domain}/api/")
    print(f"  {_bold('Health:')}     https://{domain}/health")
    print()
    print(f"  {_bold('Services:')}")
    print(f"    systemctl status khy-backend")
    print(f"    systemctl status khy-ai")
    print(f"    systemctl status khy-bridge")
    print()
    print(f"  {_bold('Logs:')}")
    print(f"    journalctl -u khy-backend -f")
    print(f"    journalctl -u khy-ai -f")
    print(f"    journalctl -u khy-bridge -f")
    print()
    print(f"  {_bold('Re-deploy:')}  sudo khy deploy --domain {domain}")
    print(f"  {_bold('Status:')}     khy deploy status")
    print()

# ── Subcommands ─────────────────────────────────────────────────────

def _deploy_full(domain: str, email: str | None = None, no_ssl: bool = False):
    """Full deployment pipeline."""
    print(_bold(f"\n  KHY Deploy → {domain}\n"))

    node = _check_prerequisites()
    _install_packages()
    dirs = _setup_backend(node)

    if not _write_nginx_config(domain):
        _fail("Nginx config validation failed. Aborting.")
        return 1

    if not no_ssl:
        _setup_ssl(domain, email)

    user = os.environ.get("SUDO_USER") or getpass.getuser()
    _create_systemd_services(node, dirs, user)
    _health_check(domain)
    _print_summary(domain)
    return 0

def _deploy_status():
    """Show deployment status."""
    _step("KHY Deploy Status")

    # Services
    for name in ["khy-backend", "khy-ai", "khy-bridge"]:
        result = _run(f"systemctl is-active {name}", check=False, capture=True)
        status = result.stdout.strip() if result.returncode == 0 else "not found"
        color = "32" if status == "active" else "31"
        print(f"  {name:20s} {_c(color, status)}")

    # Nginx
    print()
    result = _run("systemctl is-active nginx", check=False, capture=True)
    nginx_status = result.stdout.strip() if result.returncode == 0 else "not found"
    color = "32" if nginx_status == "active" else "31"
    print(f"  {'nginx':20s} {_c(color, nginx_status)}")

    # Config files
    print()
    for p in [NGINX_SITES_AVAILABLE / "khy.conf", NGINX_CONF_D / "khy.conf"]:
        if p.exists():
            print(f"  Nginx config: {p}")
            break
    else:
        print(f"  Nginx config: {_c('33', 'not found')}")

    # SSL cert
    print()
    cert_dirs = list(Path("/etc/letsencrypt/live").glob("*")) if Path("/etc/letsencrypt/live").exists() else []
    for d in cert_dirs:
        if d.is_dir() and (d / "fullchain.pem").exists():
            # Get expiry
            result = _run(
                f"openssl x509 -enddate -noout -in {d / 'fullchain.pem'}",
                check=False, capture=True,
            )
            if result.returncode == 0:
                expiry = result.stdout.strip().replace("notAfter=", "")
                print(f"  SSL cert ({d.name}): expires {expiry}")
            else:
                print(f"  SSL cert ({d.name}): exists")

    # Health
    print()
    for label, url in [("Backend", "http://127.0.0.1:3000/health"),
                        ("Bridge", "http://127.0.0.1:9222/health")]:
        result = _run(f"curl -sf {url}", check=False, capture=True)
        if result.returncode == 0:
            print(f"  {label:10s} health: {_c('32', 'OK')}")
        else:
            print(f"  {label:10s} health: {_c('31', 'FAIL')}")
    print()
    return 0

def _deploy_nginx_only(domain: str):
    """Regenerate nginx config only."""
    if not domain:
        _fail("Usage: khy deploy nginx --domain <domain>")
        return 1
    _write_nginx_config(domain)
    return 0

def _deploy_ssl_only(domain: str, email: str | None = None):
    """Setup/renew SSL only."""
    if not domain:
        _fail("Usage: khy deploy ssl --domain <domain>")
        return 1
    _setup_ssl(domain, email)
    return 0

# ── CLI Entry Point ─────────────────────────────────────────────────

def run_deploy_cli(args: list) -> int:
    """Entry point called from cli.py main()."""

    # Parse args
    domain = None
    email = None
    no_ssl = False
    subcommand = None

    i = 0
    while i < len(args):
        a = args[i]
        if a in ("--domain", "-d") and i + 1 < len(args):
            i += 1
            domain = args[i]
        elif a in ("--email", "-e") and i + 1 < len(args):
            i += 1
            email = args[i]
        elif a == "--no-ssl":
            no_ssl = True
        elif a in ("--help", "-h"):
            _print_help()
            return 0
        elif a in ("status", "nginx", "ssl") and subcommand is None:
            subcommand = a
        elif not a.startswith("-") and domain is None and subcommand is None:
            # Bare positional arg treated as domain
            domain = a
        i += 1

    if subcommand == "status":
        return _deploy_status()

    if subcommand == "nginx":
        return _deploy_nginx_only(domain)

    if subcommand == "ssl":
        return _deploy_ssl_only(domain, email)

    # Default: full deploy
    if not domain:
        _fail("Domain required. Usage: khy deploy --domain ai.khyquant.top")
        _print_help()
        return 1

    return _deploy_full(domain, email, no_ssl)

def _print_help():
    print(textwrap.dedent("""\

        KHY Deploy — one-click server deployment

        Usage:
          khy deploy --domain <domain>     Full deploy (nginx + SSL + systemd)
          khy deploy status                Show deployment status
          khy deploy nginx --domain <d>    Regenerate nginx config only
          khy deploy ssl --domain <d>      Setup/renew SSL certificate only

        Options:
          --domain, -d <domain>     Server domain name (e.g. ai.khyquant.top)
          --email, -e <email>       Email for Let's Encrypt (default: admin@<domain>)
          --no-ssl                  Skip SSL certificate setup
          --help, -h                Show this help

        Examples:
          sudo khy deploy --domain ai.khyquant.top
          sudo khy deploy --domain ai.khyquant.top --email admin@example.com
          khy deploy status
    """))
