#!/usr/bin/env python3
"""Full platform deploy: all backend services, frontend, env, DBs, catalog seed."""
from __future__ import annotations

import base64
import os
import sys
import tarfile
from pathlib import Path

import paramiko

ROOT = Path(__file__).resolve().parents[1]
APP_HOST = "192.168.10.162"
APP_USER = "morteza"
APP_PASS = "Moli@5404"
REMOTE_DIR = "/home/morteza/torbatyar"

EXCLUDE_DIRS = {
    ".git",
    "node_modules",
    ".next",
    ".venv",
    "venv",
    "__pycache__",
    ".pytest_cache",
    ".mypy_cache",
    ".ruff_cache",
    ".idea",
    ".vscode",
    ".cursor",
    "agent-transcripts",
    "scripts/.gitea-migration-staging",
}
EXCLUDE_FILES = {".DS_Store", "Thumbs.db", ".env"}

ENV_UPSERT = {
    "NEXT_PUBLIC_BACKEND_URL": "https://api.torbatyar.ir",
    "NEXT_PUBLIC_API_BASE_URL": "https://api.torbatyar.ir",
    "NEXT_PUBLIC_IDENTITY_API_URL": "https://identity.torbatyar.ir",
    "NEXT_PUBLIC_KEYCLOAK_URL": "https://auth.torbatyar.ir",
    "NEXT_PUBLIC_ACCOUNTING_API_URL": "https://accounting.torbatyar.ir",
    "NEXT_PUBLIC_CRM_API_URL": "https://crm.torbatyar.ir",
    "NEXT_PUBLIC_HEALTHCARE_API_URL": "https://healthcare.torbatyar.ir",
    "NEXT_PUBLIC_HOSPITALITY_API_URL": "https://hospitality.torbatyar.ir",
    "COMMUNICATION_SERVICE_URL": "http://communication-service:8005",
    "COMMUNICATION_DATABASE_URL": "postgresql+asyncpg://superapp:superapp_password@postgres:5432/communication_db",
    "COMMUNICATION_DATABASE_URL_SYNC": "postgresql+psycopg://superapp:superapp_password@postgres:5432/communication_db",
    "COMMUNICATION_SERVICE_NAME": "communication-service",
    "SPORTS_CENTER_SERVICE_URL": "http://sports-center-service:8006",
    "SPORTS_CENTER_DATABASE_URL": "postgresql+asyncpg://superapp:superapp_password@postgres:5432/sports_center_db",
    "SPORTS_CENTER_DATABASE_URL_SYNC": "postgresql+psycopg://superapp:superapp_password@postgres:5432/sports_center_db",
    "SPORTS_CENTER_SERVICE_NAME": "sports-center-service",
    "DELIVERY_SERVICE_URL": "http://delivery-service:8007",
    "DELIVERY_DATABASE_URL": "postgresql+asyncpg://superapp:superapp_password@postgres:5432/delivery_db",
    "DELIVERY_DATABASE_URL_SYNC": "postgresql+psycopg://superapp:superapp_password@postgres:5432/delivery_db",
    "DELIVERY_SERVICE_NAME": "delivery-service",
    "EXPERIENCE_SERVICE_URL": "http://experience-service:8008",
    "EXPERIENCE_DATABASE_URL": "postgresql+asyncpg://superapp:superapp_password@postgres:5432/experience_db",
    "EXPERIENCE_DATABASE_URL_SYNC": "postgresql+psycopg://superapp:superapp_password@postgres:5432/experience_db",
    "EXPERIENCE_SERVICE_NAME": "experience-service",
    "HOSPITALITY_SERVICE_URL": "http://hospitality-service:8009",
    "HOSPITALITY_DATABASE_URL": "postgresql+asyncpg://superapp:superapp_password@postgres:5432/hospitality_db",
    "HOSPITALITY_DATABASE_URL_SYNC": "postgresql+psycopg://superapp:superapp_password@postgres:5432/hospitality_db",
    "HOSPITALITY_SERVICE_NAME": "hospitality-service",
    "BEAUTY_BUSINESS_SERVICE_URL": "http://beauty-business-service:8011",
    "BEAUTY_BUSINESS_DATABASE_URL": "postgresql+asyncpg://superapp:superapp_password@postgres:5432/beauty_business_db",
    "BEAUTY_BUSINESS_DATABASE_URL_SYNC": "postgresql+psycopg://superapp:superapp_password@postgres:5432/beauty_business_db",
    "BEAUTY_BUSINESS_SERVICE_NAME": "beauty-business-service",
    "KEYCLOAK_SERVER_URL": "http://keycloak:8080",
    "KEYCLOAK_REALM": "superapp",
}

ALL_SERVICES = (
    "core-service identity-access-service frontend "
    "accounting-service crm-service loyalty-service communication-service "
    "sports-center-service delivery-service experience-service "
    "healthcare-service beauty-business-service hospitality-service "
    "celery-worker celery-beat"
)

EXTRA_DBS = (
    "communication_db",
    "sports_center_db",
    "delivery_db",
    "experience_db",
    "beauty_business_db",
)


def connect() -> paramiko.SSHClient:
    c = paramiko.SSHClient()
    c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    c.connect(
        APP_HOST,
        username=APP_USER,
        password=APP_PASS,
        timeout=30,
        allow_agent=False,
        look_for_keys=False,
    )
    return c


def run(c: paramiko.SSHClient, cmd: str, *, sudo: bool = False, timeout: int = 900) -> tuple[int, str]:
    cmd = cmd.replace("\r\n", "\n").replace("\r", "\n").strip() + "\n"
    b64 = base64.b64encode(cmd.encode()).decode()
    if sudo:
        full = f"echo {APP_PASS!r} | sudo -S -p '' bash -c 'echo {b64} | base64 -d | bash'"
    else:
        full = f"bash -lc 'echo {b64} | base64 -d | bash'"
    print("\n>>>", cmd[:220].replace("\n", " | "))
    _, stdout, stderr = c.exec_command(full, timeout=timeout, get_pty=True)
    out = stdout.read().decode("utf-8", "replace")
    err = stderr.read().decode("utf-8", "replace")
    code = stdout.channel.recv_exit_status()
    text = out + (("\n" + err) if err.strip() else "")
    print(text[-6000:].encode("ascii", "replace").decode("ascii"))
    return code, text


def should_exclude(path: Path) -> bool:
    if path.name in EXCLUDE_FILES or path.suffix in {".pyc", ".log", ".tar.gz", ".zip"}:
        return True
    if any(part in EXCLUDE_DIRS for part in path.parts):
        return True
    if path.as_posix().startswith("scripts/_"):
        return True
    return False


def make_tarball() -> Path:
    out = ROOT / "scripts" / "_deploy_platform_full.tar.gz"
    print(f"Creating {out} ...")
    with tarfile.open(out, "w:gz") as tar:
        for dirpath, dirnames, filenames in os.walk(ROOT):
            p = Path(dirpath)
            dirnames[:] = [d for d in dirnames if d not in EXCLUDE_DIRS]
            rel_dir = p.relative_to(ROOT)
            if should_exclude(rel_dir) and rel_dir != Path("."):
                continue
            for name in filenames:
                fp = p / name
                rel = fp.relative_to(ROOT)
                if should_exclude(rel):
                    continue
                tar.add(fp, arcname=str(rel).replace("\\", "/"))
    print(f"Size: {out.stat().st_size / 1024 / 1024:.1f} MB")
    return out


def merge_env_remote(c: paramiko.SSHClient) -> None:
    py = f"""
from pathlib import Path
p = Path({REMOTE_DIR!r}) / '.env'
text = p.read_text(encoding='utf-8') if p.exists() else ''
lines = text.splitlines()
keys = {ENV_UPSERT!r}
seen = set()
out = []
for line in lines:
    if not line.strip() or line.lstrip().startswith('#') or '=' not in line:
        out.append(line)
        continue
    k = line.split('=', 1)[0].strip()
    if k in keys:
        out.append(f"{{k}}={{keys[k]}}")
        seen.add(k)
    else:
        out.append(line)
for k, v in keys.items():
    if k not in seen:
        out.append(f"{{k}}={{v}}")
p.write_text('\\n'.join(out).rstrip() + '\\n', encoding='utf-8')
print('ENV_MERGED', len(keys))
"""
    b64 = base64.b64encode(py.encode()).decode()
    code, _ = run(c, f"python3 -c \"import base64; exec(base64.b64decode('{b64}').decode())\"")
    if code != 0:
        raise RuntimeError("env merge failed")


def main() -> int:
    tarball = make_tarball()
    c = connect()
    try:
        sftp = c.open_sftp()
        print("Uploading bundle...")
        sftp.put(str(tarball), "/tmp/torbatyar_platform_full.tar.gz")
        sftp.close()

        code, _ = run(
            c,
            f"""
set -e
cd {REMOTE_DIR}
cp -a .env /tmp/torbatyar.env.bak
tar -xzf /tmp/torbatyar_platform_full.tar.gz -C {REMOTE_DIR}
cp /tmp/torbatyar.env.bak .env
rm -f /tmp/torbatyar_platform_full.tar.gz
""",
            sudo=True,
            timeout=300,
        )
        if code != 0:
            raise RuntimeError("extract failed")

        merge_env_remote(c)

        db_cmds = " ".join(
            f"docker compose exec -T postgres psql -U superapp -d postgres -tc "
            f"\"SELECT 1 FROM pg_database WHERE datname='{db}'\" | grep -q 1 || "
            f"docker compose exec -T postgres psql -U superapp -d postgres -c 'CREATE DATABASE {db}';"
            for db in EXTRA_DBS
        )
        code, _ = run(
            c,
            f"""
set -e
cd {REMOTE_DIR}
{db_cmds}
echo DBS_READY
""",
            sudo=True,
            timeout=300,
        )
        if code != 0:
            raise RuntimeError("database create failed")

        code, _ = run(
            c,
            f"""
set -e
cd {REMOTE_DIR}
docker ps -a --format '{{{{.Names}}}}' | grep -E '_superapp_' | xargs -r docker rm -f || true
docker compose down --remove-orphans || true
docker compose up -d --build {ALL_SERVICES}
sleep 20
sg docker -c 'docker compose exec -T frontend npm install' || docker compose exec -T frontend npm install
sg docker -c 'docker compose restart frontend core-service' || docker compose restart frontend core-service
sleep 25
sg docker -c 'docker compose exec -T core-service python scripts/seed_platform_catalog.py' || docker compose exec -T core-service python scripts/seed_platform_catalog.py
""",
            sudo=True,
            timeout=3600,
        )
        if code != 0:
            raise RuntimeError("compose/seed failed")

        verify = """
set -e
cd /home/morteza/torbatyar
docker compose ps --format '{{.Service}} {{.State}}' | sort
echo '--- registry ---'
docker compose exec -T postgres psql -U superapp -d core_platform_db -t -A -c 'SELECT count(*) FROM service_registry;'
echo '--- features ---'
docker compose exec -T postgres psql -U superapp -d core_platform_db -t -A -c 'SELECT count(*) FROM features;'
for path in / /delivery/hub /communication /loyalty/hub /hospitality/hub /admin/services; do
  code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000$path)
  echo "fe$path=$code"
done
for port in 8005 8006 8007 8008; do
  curl -sf http://127.0.0.1:$port/health && echo " health:$port=ok" || echo " health:$port=fail"
done
"""
        run(c, verify, sudo=True, timeout=300)
    finally:
        c.close()

    print("\n=== PLATFORM DEPLOY DONE ===")
    print("https://torbatyar.ir")
    print("https://torbatyar.ir/admin/services")
    return 0


if __name__ == "__main__":
    try:
        raise SystemExit(main())
    except Exception as exc:
        print(f"FATAL: {exc}", file=sys.stderr)
        raise
