#!/usr/bin/env python3
"""
Daneshyar - Quick Update
Run: python scripts/update.py

Uploads new files + runs migrate + rebuilds cache + reloads Apache.
No prompts. Run and done.

Requires: pip install paramiko
"""

from __future__ import annotations

import sys
import time
import zipfile
from pathlib import Path

# ── CONFIG (must match deploy.py) ─────────────────────────────────────────────
SERVER_HOST = "91.107.131.171"
SERVER_PORT = 2435
SSH_USER    = "root"
SSH_PASS    = "V9r!k2Qp7#Lm3@tX8sN5$zHu"
APP_DIR     = "/var/www/html"
PHP_VERSION = "8.3"
# ──────────────────────────────────────────────────────────────────────────────

ROOT = Path(__file__).resolve().parents[1]


def _connect(password: str):
    try:
        import paramiko
    except ImportError:
        print("ERROR: paramiko not installed. Run: pip install paramiko")
        sys.exit(1)
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    print(f"Connecting to {SSH_USER}@{SERVER_HOST}:{SERVER_PORT} ...")
    client.connect(SERVER_HOST, port=SERVER_PORT, username=SSH_USER, password=password, timeout=30)
    client.get_transport().set_keepalive(30)
    print("Connected.\n")
    return client


def _run(client, cmd: str, *, check: bool = True) -> tuple[int, str, str]:
    print(f"  -> {cmd}")
    stdin, stdout, stderr = client.exec_command(cmd, get_pty=True)
    stdin.close()
    out_lines: list[str] = []
    for line in iter(stdout.readline, ""):
        print(f"     {line}", end="")
        out_lines.append(line)
    code = stdout.channel.recv_exit_status()
    err  = stderr.read().decode("utf-8", errors="replace")
    if check and code != 0:
        print(f"\n[FAILED] exit code {code}")
        if err.strip():
            print(err)
        sys.exit(code)
    return code, "".join(out_lines), err


def step_upload(client) -> None:
    print("=== [1/2] Upload ===\n")

    if not (ROOT / "public" / "build" / "manifest.json").exists():
        print("WARNING: public/build/manifest.json missing.")
        print("Run 'python scripts/build.py' first, then retry.")
        sys.exit(1)

    ts       = time.strftime("%Y%m%d-%H%M%S")
    zip_path = ROOT / "build" / f"daneshyar-{ts}.zip"
    zip_path.parent.mkdir(exist_ok=True)

    SKIP_DIRS  = {".git", ".idea", ".vscode", "node_modules", "build",
                  "storage/logs", "storage/framework/cache",
                  "storage/framework/sessions", "storage/framework/views", "__pycache__"}
    SKIP_FILES = {".env", ".DS_Store", "Thumbs.db"}
    SKIP_EXT   = {".zip", ".pyc"}

    print(f"  Packaging -> {zip_path.name}")
    count = 0
    with zipfile.ZipFile(zip_path, "w", compression=zipfile.ZIP_DEFLATED, compresslevel=6) as zf:
        for p in ROOT.rglob("*"):
            if p.is_dir():
                continue
            rel = p.relative_to(ROOT).as_posix()
            if any(rel == ex or rel.startswith(ex + "/") for ex in SKIP_DIRS):
                continue
            if p.name in SKIP_FILES or p.suffix in SKIP_EXT:
                continue
            zf.write(p, rel)
            count += 1
    mb = zip_path.stat().st_size / 1024 / 1024
    print(f"  {count} files, {mb:.1f} MB")

    remote_zip = f"/tmp/{zip_path.name}"
    print(f"\n  Uploading -> server:{remote_zip}")
    sftp = client.open_sftp()

    def _progress(transferred: int, total: int) -> None:
        pct = (transferred / total) * 100
        bar = "#" * int(pct / 5) + "." * (20 - int(pct / 5))
        print(f"\r  [{bar}] {pct:.0f}%", end="", flush=True)

    sftp.put(str(zip_path), remote_zip, callback=_progress)
    sftp.close()
    print()

    print(f"\n  Extracting to {APP_DIR}...")
    _run(client, f"unzip -o {remote_zip} -d {APP_DIR} > /dev/null")
    _run(client, f"rm -f {remote_zip}")
    _run(client, f"chown -R www-data:www-data {APP_DIR} || true")
    _run(client, f"find {APP_DIR} -type f -exec chmod 644 {{}} \\;")
    _run(client, f"find {APP_DIR} -type d -exec chmod 755 {{}} \\;")
    _run(client, f"chmod -R 775 {APP_DIR}/storage {APP_DIR}/bootstrap/cache 2>/dev/null || true")
    _run(client, f"chmod +x {APP_DIR}/artisan 2>/dev/null || true")

    print(f"\n[OK] Upload done.\n")


def step_update(client) -> None:
    print("=== [2/2] Migrate + Cache + Reload ===\n")
    cwd = APP_DIR
    php = f"php{PHP_VERSION}"

    print("[1/4] Composer install (new packages)...")
    _run(client, f"cd {cwd} && COMPOSER_ALLOW_SUPERUSER=1 {php} /usr/local/bin/composer install --no-dev --optimize-autoloader --no-interaction")
    _run(client, f"chown -R www-data:www-data {cwd}")

    print("\n[2/4] Migrating...")
    _run(client, f"cd {cwd} && {php} artisan migrate --force")

    print("\n[3/4] Rebuilding cache...")
    _run(client, f"cd {cwd} && {php} artisan config:clear")
    _run(client, f"cd {cwd} && {php} artisan config:cache")
    _run(client, f"cd {cwd} && {php} artisan route:cache")
    _run(client, f"cd {cwd} && {php} artisan view:cache")

    print("\n[4/4] Reloading Apache...")
    _run(client, "systemctl reload apache2")

    sep = "-" * 50
    print(f"\n{sep}")
    print("  UPDATE COMPLETE")
    print(sep)
    print(f"\n  http://{SERVER_HOST}/")
    print(sep)


def main() -> None:
    print("=" * 50)
    print("  Daneshyar - Quick Update")
    print("=" * 50)
    print(f"\n  Target: {SSH_USER}@{SERVER_HOST}:{SERVER_PORT}")
    print(f"  App:    {APP_DIR}\n")

    client = _connect(SSH_PASS)
    try:
        step_upload(client)
        step_update(client)
    finally:
        client.close()
        print("SSH connection closed.")


if __name__ == "__main__":
    main()
