#!/usr/bin/env python3
"""Deploy Healthcare service + frontend (Phases 13.0–13.7) to production."""
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"
NGX_HOST = "192.168.10.156"
NGX_USER = "torbatyaruser"
NGX_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",
    "agent-transcripts",
}
EXCLUDE_FILES = {".DS_Store", "Thumbs.db", ".env"}

ENV_UPSERT = {
    "NEXT_PUBLIC_HEALTHCARE_API_URL": "https://healthcare.torbatyar.ir",
    "HEALTHCARE_SERVICE_URL": "http://healthcare-service:8010",
    "HEALTHCARE_DATABASE_URL": "postgresql+asyncpg://superapp:superapp_password@postgres:5432/healthcare_db",
    "HEALTHCARE_DATABASE_URL_SYNC": "postgresql+psycopg://superapp:superapp_password@postgres:5432/healthcare_db",
    "HEALTHCARE_SERVICE_NAME": "healthcare-service",
    "KEYCLOAK_SERVER_URL": "http://keycloak:8080",
    "KEYCLOAK_REALM": "superapp",
}


def connect(host: str, user: str, password: str) -> paramiko.SSHClient:
    c = paramiko.SSHClient()
    c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    c.connect(
        host,
        username=user,
        password=password,
        timeout=30,
        allow_agent=False,
        look_for_keys=False,
    )
    return c


def run(
    client: paramiko.SSHClient,
    cmd: str,
    *,
    sudo: bool = False,
    password: str = "",
    timeout: int = 600,
) -> 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 {password!r} | sudo -S -p '' bash -c 'echo {b64} | base64 -d | bash'"
    else:
        full = f"bash -lc 'echo {b64} | base64 -d | bash'"
    print(
        f"\n>>> {'[sudo] ' if sudo else ''}{cmd[:220].replace(chr(10), ' | ')}"
        f"{'...' if len(cmd) > 220 else ''}"
    )
    _stdin, stdout, stderr = client.exec_command(full, timeout=timeout, get_pty=True)
    out = stdout.read().decode("utf-8", errors="replace")
    err = stderr.read().decode("utf-8", errors="replace")
    code = stdout.channel.recv_exit_status()
    text = out + (("\n" + err) if err.strip() else "")
    printable = text[-5000:] if len(text) > 5000 else text
    try:
        print(printable)
    except UnicodeEncodeError:
        print(printable.encode("ascii", "replace").decode("ascii"))
    return code, text


def should_exclude(path: Path) -> bool:
    parts = set(path.parts)
    if parts & EXCLUDE_DIRS:
        return True
    if path.name in EXCLUDE_FILES:
        return True
    if path.suffix in {".pyc", ".log"}:
        return True
    return False


def make_tarball() -> Path:
    out = ROOT / "scripts" / "_deploy_healthcare_bundle.tar.gz"
    print(f"Creating tarball {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 and not d.startswith(".cursor")
            ]
            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
                if rel.as_posix().startswith("scripts/_deploy"):
                    continue
                tar.add(fp, arcname=str(rel).replace("\\", "/"))
    print(f"Tarball size: {out.stat().st_size / 1024 / 1024:.1f} MB")
    return out


def upload_file(client: paramiko.SSHClient, local: Path, remote: str) -> None:
    sftp = client.open_sftp()
    print(f"Uploading {local} -> {remote}")
    sftp.put(str(local), remote)
    sftp.close()


def put_text_sudo(
    client: paramiko.SSHClient, password: str, remote: str, content: str
) -> None:
    b64 = base64.b64encode(content.encode()).decode()
    cmd = (
        f"mkdir -p $(dirname {remote!r}) && echo {b64} | base64 -d > {remote!r} "
        f"&& chmod 644 {remote!r}"
    )
    code, _ = run(client, cmd, sudo=True, password=password, timeout=60)
    if code != 0:
        raise RuntimeError(f"failed writing {remote}")


def merge_env_remote(client: 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(
        client,
        f"python3 -c \"import base64; exec(base64.b64decode('{b64}').decode())\"",
    )
    if code != 0:
        raise RuntimeError("env merge failed")


def compose_up(client: paramiko.SSHClient, services: str) -> None:
    code, _ = run(
        client,
        f"""
set -e
cd {REMOTE_DIR}
sg docker -c 'docker compose up -d --build {services}'
sg docker -c 'docker compose ps'
""",
        timeout=1800,
    )
    if code != 0:
        code, _ = run(
            client,
            f"""
set -e
cd {REMOTE_DIR}
docker compose up -d --build {services}
docker compose ps
""",
            sudo=True,
            password=APP_PASS,
            timeout=1800,
        )
        if code != 0:
            raise RuntimeError(f"compose up failed for {services}")


def main() -> int:
    tarball = make_tarball()

    app = connect(APP_HOST, APP_USER, APP_PASS)
    try:
        run(app, f"mkdir -p {REMOTE_DIR}")
        upload_file(app, tarball, "/tmp/torbatyar_healthcare_bundle.tar.gz")
        code, _ = run(
            app,
            f"""
set -e
cd {REMOTE_DIR}
if [ -f .env ]; then cp .env /tmp/torbatyar.env.bak; fi
chown -R {APP_USER}:{APP_USER} {REMOTE_DIR}/backend {REMOTE_DIR}/frontend {REMOTE_DIR}/docs {REMOTE_DIR}/docker-compose.yml {REMOTE_DIR}/infrastructure 2>/dev/null || true
chmod -R u+rwX {REMOTE_DIR}/backend {REMOTE_DIR}/frontend {REMOTE_DIR}/docs 2>/dev/null || true
tar --overwrite -xzf /tmp/torbatyar_healthcare_bundle.tar.gz -C {REMOTE_DIR}
if [ -f /tmp/torbatyar.env.bak ]; then cp /tmp/torbatyar.env.bak .env; fi
chown -R {APP_USER}:{APP_USER} {REMOTE_DIR}/backend {REMOTE_DIR}/frontend {REMOTE_DIR}/docs 2>/dev/null || true
rm -f /tmp/torbatyar_healthcare_bundle.tar.gz
ls -la {REMOTE_DIR}/backend/services/healthcare | head
ls -la {REMOTE_DIR}/frontend/app/healthcare | head
""",
            sudo=True,
            password=APP_PASS,
            timeout=300,
        )
        if code != 0:
            raise RuntimeError("extract failed")

        merge_env_remote(app)

        run(
            app,
            f"""
set -e
cd {REMOTE_DIR}
sg docker -c "docker compose exec -T postgres psql -U superapp -d postgres -tc \\"SELECT 1 FROM pg_database WHERE datname='healthcare_db'\\" | grep -q 1 || docker compose exec -T postgres psql -U superapp -d postgres -c 'CREATE DATABASE healthcare_db;'"
""",
            timeout=120,
        )

        compose_up(app, "healthcare-service frontend")

        run(
            app,
            f"""
set -e
cd {REMOTE_DIR}
sg docker -c 'docker compose exec -T frontend npm install' || docker compose exec -T frontend npm install
sg docker -c 'docker compose restart frontend' || docker compose restart frontend
sleep 15
sg docker -c 'docker compose logs --tail=80 healthcare-service frontend' || docker compose logs --tail=80 healthcare-service frontend
""",
            sudo=True,
            password=APP_PASS,
            timeout=1800,
        )

        code, health = run(
            app,
            """
set -e
for i in 1 2 3 4 5 6 7 8 9 10; do
  if curl -sf http://127.0.0.1:8010/health; then
    echo
    echo HEALTHCARE_HEALTH_OK
    exit 0
  fi
  sleep 3
done
curl -sv http://127.0.0.1:8010/health || true
exit 1
""",
            timeout=120,
        )
        if code != 0:
            raise RuntimeError(f"Healthcare health failed: {health}")

        code, fe = run(
            app,
            """
curl -s -o /dev/null -w 'healthcare_hub=%{http_code}\n' http://127.0.0.1:3000/healthcare/hub
""",
            timeout=60,
        )
        if code != 0:
            raise RuntimeError(f"Frontend healthcare route failed: {fe}")
    finally:
        app.close()

    ngx = connect(NGX_HOST, NGX_USER, NGX_PASS)
    try:
        conf = (ROOT / "infrastructure" / "nginx" / "torbatyar.ir.conf").read_text(
            encoding="utf-8"
        )
        put_text_sudo(ngx, NGX_PASS, "/etc/nginx/conf.d/torbatyar.ir.conf", conf)
        certbot = r"""
set -e
mkdir -p /var/www/html
certbot certonly --webroot -w /var/www/html \
  -d torbatyar.ir -d www.torbatyar.ir \
  -d api.torbatyar.ir -d identity.torbatyar.ir -d auth.torbatyar.ir \
  -d accounting.torbatyar.ir -d crm.torbatyar.ir -d loyalty.torbatyar.ir \
  -d healthcare.torbatyar.ir \
  --non-interactive --agree-tos --email support@torbatyar.ir --expand || true
nginx -t && systemctl reload nginx && echo NGINX_RELOADED
"""
        code, _ = run(ngx, certbot, sudo=True, password=NGX_PASS, timeout=300)
        if code != 0:
            raise RuntimeError("nginx reload / certbot failed")
    finally:
        ngx.close()

    print("\n=== HEALTHCARE DEPLOY FINISHED ===")
    print("API: https://healthcare.torbatyar.ir/health")
    print("Hub: https://torbatyar.ir/healthcare/hub")
    print("Local API: http://192.168.10.162:8010/health")
    return 0


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