#!/usr/bin/env python3
"""صدور/گسترش گواهی Let's Encrypt برای یک دامنه tenant و فعال‌سازی redirect HTTPS.

اجرا روی سرور nginx (با root/sudo):
    python3 /opt/torbatyar/bin/provision_ssl.py example.torbatyar.ir
"""
from __future__ import annotations

import fcntl
import os
import re
import subprocess
import sys
from pathlib import Path

CERT_NAME = os.environ.get("TORBATYAR_CERT_NAME", "torbatyar.ir")
CERT_PATH = Path(f"/etc/letsencrypt/live/{CERT_NAME}/fullchain.pem")
WEBROOT = os.environ.get("TORBATYAR_WEBROOT", "/var/www/html")
EMAIL = os.environ.get("TORBATYAR_CERTBOT_EMAIL", "support@torbatyar.ir")
MAP_FILE = Path(
    os.environ.get("TORBATYAR_SSL_MAP", "/etc/nginx/conf.d/torbatyar-tenant-ssl.map")
)
LOCK_FILE = Path("/var/lock/torbatyar-ssl-provision.lock")
PLATFORM_HOSTS = {
    "torbatyar.ir",
    "www.torbatyar.ir",
    "api.torbatyar.ir",
    "identity.torbatyar.ir",
    "auth.torbatyar.ir",
    "accounting.torbatyar.ir",
}


def die(msg: str, code: int = 1) -> None:
    print(f"ERROR: {msg}", file=sys.stderr)
    raise SystemExit(code)


def normalize_domain(raw: str) -> str:
    host = raw.strip().lower().split("/")[0].split(":")[0]
    if not re.fullmatch(r"[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+", host):
        die(f"invalid domain: {raw}")
    return host


def current_sans() -> set[str]:
    if not CERT_PATH.exists():
        return set(PLATFORM_HOSTS)
    out = subprocess.check_output(
        ["openssl", "x509", "-in", str(CERT_PATH), "-noout", "-ext", "subjectAltName"],
        text=True,
    )
    found = set(re.findall(r"DNS:([^,\s]+)", out))
    return found or set(PLATFORM_HOSTS)


def ensure_map_entry(domain: str) -> None:
    MAP_FILE.parent.mkdir(parents=True, exist_ok=True)
    if MAP_FILE.exists():
        text = MAP_FILE.read_text(encoding="utf-8")
    else:
        text = (
            "# Auto-managed by provision_ssl.py — do not edit by hand\n"
            "map $host $torbatyar_tenant_ssl {\n"
            "    default 0;\n"
            "}\n"
        )
    if re.search(rf"(?m)^\s*{re.escape(domain)}\s+1\s*;", text):
        return
    if "map $host $torbatyar_tenant_ssl" not in text:
        die(f"unexpected map file format: {MAP_FILE}")
    # درج قبل از بستن }
    text = re.sub(
        r"(map \$host \$torbatyar_tenant_ssl \{.*?)(\n\})",
        rf"\1\n    {domain} 1;\2",
        text,
        count=1,
        flags=re.S,
    )
    MAP_FILE.write_text(text, encoding="utf-8")
    print(f"map_updated={domain}")


def run_certbot(domains: list[str]) -> None:
    cmd = [
        "certbot",
        "certonly",
        "--webroot",
        "-w",
        WEBROOT,
        "--expand",
        "--non-interactive",
        "--agree-tos",
        "--email",
        EMAIL,
        "--cert-name",
        CERT_NAME,
    ]
    for d in domains:
        cmd.extend(["-d", d])
    print("certbot:", " ".join(cmd))
    subprocess.check_call(cmd)


def reload_nginx() -> None:
    subprocess.check_call(["nginx", "-t"])
    subprocess.check_call(["systemctl", "reload", "nginx"])
    print("nginx_reloaded")


def provision(domain: str) -> None:
    domain = normalize_domain(domain)
    LOCK_FILE.parent.mkdir(parents=True, exist_ok=True)
    with LOCK_FILE.open("a+", encoding="utf-8") as lock:
        fcntl.flock(lock.fileno(), fcntl.LOCK_EX)
        sans = current_sans()
        if domain not in sans:
            ordered = sorted(PLATFORM_HOSTS | sans | {domain})
            # platform hosts اول برای پایداری cert-name
            preferred = [h for h in sorted(PLATFORM_HOSTS) if h in ordered]
            rest = [h for h in ordered if h not in preferred]
            run_certbot(preferred + rest)
        else:
            print(f"cert_already_has={domain}")
        if domain not in PLATFORM_HOSTS:
            ensure_map_entry(domain)
        reload_nginx()
        print(f"OK {domain}")


def main() -> None:
    if len(sys.argv) != 2:
        die(f"usage: {sys.argv[0]} <domain>")
    if os.geteuid() != 0:
        die("must run as root")
    provision(sys.argv[1])


if __name__ == "__main__":
    main()
