#!/usr/bin/env python3
"""
AliothPress — Bootstrap Installer
==================================
Run once on a fresh server:  sudo python3 install.py

What it does:
  1. Checks Python version (3.10+ required)
  2. Installs system packages (Nginx, etc.) — if on Linux with apt
  3. Creates a virtual environment & installs Python dependencies
  4. Creates .env file with a secure SECRET_KEY
  5. Server setup: Gunicorn + Nginx + systemd + auto-restart
  6. Starts the application — open your domain in the browser to continue setup

Works on:
  • Hetzner Cloud, DigitalOcean, Linode, any Ubuntu/Debian VPS → full server setup
  • macOS / Windows / local machine → development mode (Flask dev server)

No flags, no options. Just run it.
"""

import sys
import os
import subprocess
import shutil

# ── Colours for terminal output ──────────────────────────────────────────────
GREEN  = '\033[92m'
YELLOW = '\033[93m'
RED    = '\033[91m'
CYAN   = '\033[96m'
BOLD   = '\033[1m'
DIM    = '\033[2m'
RESET  = '\033[0m'

def printc(colour, symbol, message):
    """Print a coloured status line."""
    print(f"  {colour}{symbol}{RESET}  {message}")

def header(text):
    print(f"\n{BOLD}{CYAN}{'─' * 56}{RESET}")
    print(f"  {BOLD}{text}{RESET}")
    print(f"{CYAN}{'─' * 56}{RESET}\n")

def fail(message):
    printc(RED, '✗', message)
    sys.exit(1)

def ask(prompt, default=''):
    """Prompt user for input with an optional default."""
    if default:
        result = input(f"  {prompt} [{default}]: ").strip()
        return result if result else default
    else:
        return input(f"  {prompt}: ").strip()

def run_cmd(cmd, check=True, quiet=False):
    """Run a shell command, return result."""
    result = subprocess.run(
        cmd, shell=isinstance(cmd, str),
        capture_output=quiet, text=True
    )
    if check and result.returncode != 0:
        if quiet:
            fail(f'Command failed: {cmd}\n{result.stderr}')
    return result


# ── Locate project root (same directory as this script) ──────────────────────
PROJECT_DIR = os.path.dirname(os.path.abspath(__file__))
os.chdir(PROJECT_DIR)

REQUIREMENTS_FILE = os.path.join(PROJECT_DIR, 'requirements.txt')
ENV_FILE          = os.path.join(PROJECT_DIR, '.env')
ENV_EXAMPLE       = os.path.join(PROJECT_DIR, '.env.example')
VENV_DIR          = os.path.join(PROJECT_DIR, 'venv')
APP_FILE          = os.path.join(PROJECT_DIR, 'app.py')


# ══════════════════════════════════════════════════════════════════════════════
#  STEP 1 — Check Python version & detect environment
# ══════════════════════════════════════════════════════════════════════════════
header('AliothPress — Installation')

major, minor = sys.version_info[:2]
printc(GREEN, '●', f'Python {major}.{minor} detected')

if (major, minor) < (3, 10):
    fail(f'Python 3.10 or higher is required. You have {major}.{minor}.')
else:
    printc(GREEN, '✓', 'Python version OK')

# Detect environment
IS_LINUX = sys.platform.startswith('linux')
IS_ROOT  = os.geteuid() == 0 if IS_LINUX else False
HAS_APT  = shutil.which('apt-get') is not None
HAS_SYSTEMD = os.path.isdir('/run/systemd/system') if IS_LINUX else False

CAN_PRODUCTION = IS_LINUX and HAS_APT and HAS_SYSTEMD

if CAN_PRODUCTION:
    printc(GREEN, '●', 'Linux server detected — full setup will be automatic')
    if not IS_ROOT:
        printc(YELLOW, '!', 'Not running as root. Re-run with: sudo python3 install.py')
        printc(YELLOW, ' ', 'Continuing in local mode...')
        CAN_PRODUCTION = False
else:
    printc(GREEN, '●', 'Local environment detected')


# ══════════════════════════════════════════════════════════════════════════════
#  STEP 2 — System packages (Linux server only)
# ══════════════════════════════════════════════════════════════════════════════
if CAN_PRODUCTION:
    header('Installing Server Software')

    printc(YELLOW, '…', 'Updating package lists...')
    run_cmd('apt-get update -qq', quiet=True)

    packages = {
        'nginx': 'nginx',
        'python3-venv': 'python3-venv',
        'python3-dev': 'python3-dev',
        'build-essential': 'build-essential',
        'certbot': 'certbot',
        'python3-certbot-nginx': 'python3-certbot-nginx',
    }

    to_install = []
    for pkg_name, apt_name in packages.items():
        result = subprocess.run(
            ['dpkg', '-s', apt_name],
            capture_output=True, text=True
        )
        if result.returncode != 0:
            to_install.append(apt_name)

    if to_install:
        printc(YELLOW, '…', f'Installing: {", ".join(to_install)}')
        run_cmd(f'apt-get install -y -qq {" ".join(to_install)}', quiet=True)
        printc(GREEN, '✓', 'Server software installed')
    else:
        printc(GREEN, '✓', 'Server software already installed')


# ══════════════════════════════════════════════════════════════════════════════
#  STEP 3 — Virtual environment & dependencies
# ══════════════════════════════════════════════════════════════════════════════
header('Installing Dependencies')

in_venv = (
    hasattr(sys, 'real_prefix') or
    (hasattr(sys, 'base_prefix') and sys.base_prefix != sys.prefix)
)

if in_venv:
    printc(GREEN, '✓', 'Already running inside a virtual environment')
    PIP = shutil.which('pip') or shutil.which('pip3')
    PYTHON = sys.executable
else:
    if os.path.isdir(VENV_DIR):
        printc(GREEN, '✓', 'Virtual environment already exists at ./venv')
    else:
        printc(YELLOW, '…', 'Creating virtual environment in ./venv')
        result = subprocess.run([sys.executable, '-m', 'venv', VENV_DIR],
                                capture_output=True, text=True)
        if result.returncode != 0:
            fail(f'Failed to create venv: {result.stderr.strip()}')
        printc(GREEN, '✓', 'Virtual environment created')

    if os.name == 'nt':
        PIP    = os.path.join(VENV_DIR, 'Scripts', 'pip.exe')
        PYTHON = os.path.join(VENV_DIR, 'Scripts', 'python.exe')
    else:
        PIP    = os.path.join(VENV_DIR, 'bin', 'pip')
        PYTHON = os.path.join(VENV_DIR, 'bin', 'python')

if not os.path.isfile(REQUIREMENTS_FILE):
    fail('requirements.txt not found. Make sure install.py is in the project root.')

printc(YELLOW, '…', 'Upgrading pip...')
subprocess.run([PIP, 'install', '--upgrade', 'pip', '--quiet'],
               capture_output=True, text=True)

printc(YELLOW, '…', 'Installing packages from requirements.txt...')
result = subprocess.run(
    [PIP, 'install', '-r', REQUIREMENTS_FILE],
    capture_output=False, text=True
)

if result.returncode != 0:
    fail('Some packages failed to install. Check the output above.')
else:
    printc(GREEN, '✓', 'All dependencies installed')

if CAN_PRODUCTION:
    printc(YELLOW, '…', 'Installing Gunicorn...')
    subprocess.run([PIP, 'install', 'gunicorn', '--quiet'],
                   capture_output=True, text=True)
    printc(GREEN, '✓', 'Gunicorn installed')


# ══════════════════════════════════════════════════════════════════════════════
#  STEP 4 — Prepare .env
# ══════════════════════════════════════════════════════════════════════════════
header('Environment Configuration')

if os.path.isfile(ENV_FILE):
    printc(GREEN, '✓', '.env file already exists — keeping it')
    env_lines = open(ENV_FILE).read()
else:
    if os.path.isfile(ENV_EXAMPLE):
        shutil.copy2(ENV_EXAMPLE, ENV_FILE)
        printc(GREEN, '✓', 'Created .env from .env.example')
    else:
        import secrets
        with open(ENV_FILE, 'w') as f:
            f.write(f"SECRET_KEY={secrets.token_hex(32)}\n")
            f.write("DATABASE_URI=sqlite:///aliothpress.db\n")
            f.write("# UPDATE_SERVER_URL=https://updates.aliothpress.com\n")
        printc(GREEN, '✓', 'Created .env with a secure SECRET_KEY')
    env_lines = open(ENV_FILE).read()

# Ensure SECRET_KEY exists
if 'SECRET_KEY=' not in env_lines or env_lines.split('SECRET_KEY=')[1].split('\n')[0].strip() == '':
    import secrets
    with open(ENV_FILE, 'a') as f:
        f.write(f"\nSECRET_KEY={secrets.token_hex(32)}\n")
    printc(GREEN, '✓', 'Generated secure SECRET_KEY')


# ══════════════════════════════════════════════════════════════════════════════
#  STEP 5 — Server setup (Gunicorn + Nginx + systemd)
# ══════════════════════════════════════════════════════════════════════════════
if CAN_PRODUCTION:
    header('Server Configuration')

    # ── Ask for domain ───────────────────────────────────────────────
    print(f"  {DIM}Enter your domain (e.g., mysite.com) or server IP address.")
    print(f"  If you don't have a domain yet, use your server IP.{RESET}")
    print()
    domain = ask('Domain or IP', default='_')

    # Auto-detect IP if user left default
    if domain == '_':
        try:
            import urllib.request
            domain = urllib.request.urlopen('https://ifconfig.me', timeout=5).read().decode().strip()
            printc(GREEN, '✓', f'Detected server IP: {domain}')
        except Exception:
            domain = '_'

    # ── Determine user ───────────────────────────────────────────────
    APP_USER = os.getenv('SUDO_USER', 'aliothpress')
    if APP_USER == 'root':
        APP_USER = 'aliothpress'

    result = subprocess.run(['id', APP_USER], capture_output=True, text=True)
    if result.returncode != 0:
        printc(YELLOW, '…', f'Creating system user: {APP_USER}')
        run_cmd(f'useradd -r -m -s /bin/bash {APP_USER}', quiet=True)
        printc(GREEN, '✓', f'User {APP_USER} created')
    else:
        printc(GREEN, '✓', f'User {APP_USER} exists')

    run_cmd(f'chown -R {APP_USER}:{APP_USER} {PROJECT_DIR}', quiet=True)

    # ── Gunicorn config ──────────────────────────────────────────────
    GUNICORN_BIN = os.path.join(VENV_DIR, 'bin', 'gunicorn')
    WORKERS = max(2, min(4, (os.cpu_count() or 1) * 2 + 1))
    BIND_SOCKET = os.path.join(PROJECT_DIR, 'aliothpress.sock')

    # ── systemd service ──────────────────────────────────────────────
    SERVICE_NAME = 'aliothpress'
    SERVICE_FILE = f'/etc/systemd/system/{SERVICE_NAME}.service'

    service_content = f"""[Unit]
Description=AliothPress CMS
After=network.target

[Service]
Type=notify
User={APP_USER}
Group={APP_USER}
WorkingDirectory={PROJECT_DIR}
Environment="PATH={VENV_DIR}/bin:/usr/local/bin:/usr/bin:/bin"
ExecStart={GUNICORN_BIN} \\
    --workers {WORKERS} \\
    --bind unix:{BIND_SOCKET} \\
    --timeout 120 \\
    --access-logfile /var/log/aliothpress/access.log \\
    --error-logfile /var/log/aliothpress/error.log \\
    app:app
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
RestartSec=5

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

    os.makedirs('/var/log/aliothpress', exist_ok=True)
    run_cmd(f'chown {APP_USER}:{APP_USER} /var/log/aliothpress', quiet=True)

    with open(SERVICE_FILE, 'w') as f:
        f.write(service_content)
    printc(GREEN, '✓', 'Background service configured')

    # ── Nginx config ─────────────────────────────────────────────────
    NGINX_CONF = f'/etc/nginx/sites-available/{SERVICE_NAME}'
    NGINX_LINK = f'/etc/nginx/sites-enabled/{SERVICE_NAME}'

    nginx_content = f"""server {{
    listen 80;
    server_name {domain};

    client_max_body_size 20M;

    location /static/ {{
        alias {PROJECT_DIR}/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }}

    location /favicon.ico {{
        alias {PROJECT_DIR}/static/uploads/favicon/favicon.ico;
        expires 30d;
    }}

    location / {{
        proxy_pass http://unix:{BIND_SOCKET};
        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 120;
    }}
}}
"""

    with open(NGINX_CONF, 'w') as f:
        f.write(nginx_content)

    if os.path.islink(NGINX_LINK) or os.path.isfile(NGINX_LINK):
        os.remove(NGINX_LINK)
    os.symlink(NGINX_CONF, NGINX_LINK)

    default_conf = '/etc/nginx/sites-enabled/default'
    if os.path.islink(default_conf):
        os.remove(default_conf)

    printc(GREEN, '✓', 'Web server configured')

    result = run_cmd('nginx -t', quiet=True, check=False)
    if result.returncode != 0:
        printc(RED, '!', 'Nginx config test failed — check /etc/nginx/ manually')
    else:
        printc(GREEN, '✓', 'Nginx config OK')

    # ── sudoers for auto-restart & wizard PostgreSQL auto-setup ─────
    SUDOERS_FILE = f'/etc/sudoers.d/{SERVICE_NAME}'
    sudoers_content = f"{APP_USER} ALL=(ALL) NOPASSWD: /bin/systemctl restart {SERVICE_NAME}.service\n"
    sudoers_content += f"{APP_USER} ALL=(ALL) NOPASSWD: /bin/systemctl reload {SERVICE_NAME}.service\n"
    sudoers_content += f"{APP_USER} ALL=(postgres) NOPASSWD: /usr/bin/psql\n"
    # Wizard auto-setup: install & manage PostgreSQL without terminal
    sudoers_content += f"{APP_USER} ALL=(ALL) NOPASSWD: /usr/bin/apt-get update *\n"
    sudoers_content += f"{APP_USER} ALL=(ALL) NOPASSWD: /usr/bin/apt-get install *\n"
    sudoers_content += f"{APP_USER} ALL=(ALL) NOPASSWD: /bin/systemctl start postgresql\n"
    sudoers_content += f"{APP_USER} ALL=(ALL) NOPASSWD: /bin/systemctl enable postgresql\n"
    # SSL setup from admin panel
    sudoers_content += f"{APP_USER} ALL=(ALL) NOPASSWD: /usr/local/bin/aliothpress-ssl\n"
    # Update-prep: restore ownership before writing updates
    sudoers_content += f"{APP_USER} ALL=(ALL) NOPASSWD: /usr/local/bin/aliothpress-update-prep\n"

    with open(SUDOERS_FILE, 'w') as f:
        f.write(sudoers_content)
    os.chmod(SUDOERS_FILE, 0o440)
    printc(GREEN, '✓', 'Auto-update permissions configured')

    # ── Update-prep helper script ──────────────────────────────────────
    # Runs `chown -R` on the app directory to restore ownership to the
    # service user before the updater writes files. Hardcoded paths,
    # zero arguments — no injection surface. Called via sudoers by the
    # updater (same pattern as the SSL helper).
    UPDATE_PREP_PATH = '/usr/local/bin/aliothpress-update-prep'
    update_prep_script = f"""#!/bin/bash
# AliothPress Update-Prep Helper — runs as root via sudoers.
# Restores ownership of app files to the service user so the updater
# can overwrite files that may have been edited by root via SSH/SFTP.
set -e

APP_DIR="{PROJECT_DIR}"
APP_USER="{APP_USER}"

if [ ! -d "$APP_DIR" ]; then
    echo "ERROR: App directory not found at $APP_DIR"
    exit 1
fi

if ! id -u "$APP_USER" >/dev/null 2>&1; then
    echo "ERROR: Service user $APP_USER not found"
    exit 1
fi

chown -R "$APP_USER:$APP_USER" "$APP_DIR"
echo "OK: Ownership restored on $APP_DIR"
exit 0
"""
    with open(UPDATE_PREP_PATH, 'w') as f:
        f.write(update_prep_script)
    os.chmod(UPDATE_PREP_PATH, 0o755)
    printc(GREEN, '✓', 'Update helper installed')

    # ── Save SERVICE_NAME to .env ────────────────────────────────────
    env_content = open(ENV_FILE).read()
    if 'SERVICE_NAME=' not in env_content:
        with open(ENV_FILE, 'a') as f:
            f.write(f"\nSERVICE_NAME={SERVICE_NAME}.service\n")

    run_cmd(f'chown {APP_USER}:{APP_USER} {ENV_FILE}', quiet=True)

    # ── Start everything ─────────────────────────────────────────────
    printc(YELLOW, '…', 'Starting services...')

    run_cmd('systemctl daemon-reload', quiet=True)
    run_cmd(f'systemctl enable {SERVICE_NAME}', quiet=True)
    run_cmd(f'systemctl start {SERVICE_NAME}', quiet=True)
    run_cmd('systemctl reload nginx', quiet=True)

    import time
    time.sleep(2)
    result = run_cmd(f'systemctl is-active {SERVICE_NAME}', quiet=True, check=False)
    if result.stdout.strip() == 'active':
        printc(GREEN, '✓', 'AliothPress is running!')
    else:
        printc(RED, '!', 'Service may not have started. Check: journalctl -u aliothpress -n 30')

    run_cmd('systemctl restart nginx', quiet=True)
    printc(GREEN, '✓', 'Nginx restarted')

    # ── Done ─────────────────────────────────────────────────────────
    header('Installation Complete!')

    url = f'http://{domain}'

    print(f"""
  {GREEN}{BOLD}Your site is live!{RESET}

  {BOLD}→ Open in your browser:{RESET}
    {url}

  The Setup Wizard will guide you through the rest
  (admin account, site name, license key).

  {CYAN}HTTPS (recommended):{RESET}
    sudo certbot --nginx -d {domain}
    {DIM}Free SSL from Let's Encrypt — takes 30 seconds.{RESET}

  {CYAN}Useful commands:{RESET}
    sudo systemctl status aliothpress   {DIM}# check status{RESET}
    sudo systemctl restart aliothpress  {DIM}# restart{RESET}
    journalctl -u aliothpress -f        {DIM}# live logs{RESET}
""")

    update_server_file = os.path.join(PROJECT_DIR, 'update_server.py')
    if os.path.isfile(update_server_file):
        print(f"  {DIM}Note: update_server.py detected. This should be deployed")
        print(f"  separately on your own server, not on customer machines.{RESET}")
        print()

    sys.exit(0)


# ══════════════════════════════════════════════════════════════════════════════
#  Development mode (macOS, Windows, or non-root Linux)
# ══════════════════════════════════════════════════════════════════════════════
header('Starting AliothPress')

print(f"""
  {GREEN}{BOLD}Installation complete!{RESET}

  The application is starting on port {BOLD}5000{RESET}.

  {BOLD}→ Open in your browser:{RESET}
    http://localhost:5000

  The Setup Wizard will guide you through creating
  your admin account, choosing a theme, and naming your site.

  {YELLOW}Note:{RESET} This is the development server.
  For a live server, re-run as root:
    sudo python3 install.py

  Press {BOLD}Ctrl+C{RESET} to stop the server.
""")

os.execvp(PYTHON, [PYTHON, APP_FILE])
