"""Bootstrap PostgreSQL: 建用户 + 建库 + 授权 + 写 .env + 跑迁移。

新机器初始化用。前置:已装 PostgreSQL 16 + 知道 postgres 超级用户密码。

用法:
    python scripts/init_postgres.py

环境变量(可选,不填会交互式问):
    PG_HOST          PG 主机, 默认 localhost
    PG_PORT          PG 端口, 默认 5432
    PG_SUPER_USER    超级用户, 默认 postgres
    PG_SUPER_PASS    超级用户密码 (不填会 getpass 交互输入)
    APP_DB_NAME      业务库名, 默认 shaguabijia
    APP_DB_USER      业务用户名, 默认 shaguabijia_app
    APP_DB_PASS      业务用户密码 (不填会自动生成强密码)

幂等:已存在的用户/库不会重复建,已配的 .env 会被覆盖前提示。
"""
from __future__ import annotations

import getpass
import os
import secrets
import subprocess
import sys
from pathlib import Path

try:
    import psycopg
    from psycopg import sql
except ImportError:
    print("❌ 缺 psycopg。先跑: pip install -e .")
    sys.exit(1)


ROOT = Path(__file__).resolve().parent.parent
ENV_FILE = ROOT / ".env"
ENV_EXAMPLE = ROOT / ".env.example"


def env_or_input(key: str, prompt: str, default: str = "", secret: bool = False) -> str:
    val = os.environ.get(key, "").strip()
    if val:
        return val
    if secret:
        return getpass.getpass(f"{prompt}: ").strip()
    suffix = f" [{default}]" if default else ""
    raw = input(f"{prompt}{suffix}: ").strip()
    return raw or default


def ensure_role(conn: psycopg.Connection, user: str, password: str) -> None:
    # 注意: CREATE/ALTER USER 不支持参数化密码, 必须用 sql.Literal 内联
    with conn.cursor() as cur:
        cur.execute("SELECT 1 FROM pg_roles WHERE rolname=%s", (user,))
        if cur.fetchone():
            print(f"  · 用户 {user} 已存在, 重置密码")
            cur.execute(
                sql.SQL("ALTER USER {} WITH PASSWORD {}").format(
                    sql.Identifier(user), sql.Literal(password)
                )
            )
        else:
            print(f"  · 创建用户 {user}")
            cur.execute(
                sql.SQL("CREATE USER {} WITH PASSWORD {}").format(
                    sql.Identifier(user), sql.Literal(password)
                )
            )


def ensure_database(conn: psycopg.Connection, db: str, owner: str) -> None:
    with conn.cursor() as cur:
        cur.execute("SELECT 1 FROM pg_database WHERE datname=%s", (db,))
        if cur.fetchone():
            print(f"  · 数据库 {db} 已存在")
            return
        print(f"  · 创建数据库 {db} (owner={owner}, encoding=UTF8)")
        cur.execute(
            sql.SQL("CREATE DATABASE {} OWNER {} ENCODING 'UTF8'").format(
                sql.Identifier(db), sql.Identifier(owner)
            )
        )


def grant_all(conn: psycopg.Connection, db: str, user: str) -> None:
    with conn.cursor() as cur:
        cur.execute(
            sql.SQL("GRANT ALL PRIVILEGES ON DATABASE {} TO {}").format(
                sql.Identifier(db), sql.Identifier(user)
            )
        )


def write_env(database_url: str) -> None:
    if not ENV_FILE.exists():
        if ENV_EXAMPLE.exists():
            ENV_FILE.write_text(ENV_EXAMPLE.read_text(encoding="utf-8"), encoding="utf-8")
            print(f"  · 从 .env.example 复制出 .env")
        else:
            ENV_FILE.write_text("", encoding="utf-8")

    lines = ENV_FILE.read_text(encoding="utf-8").splitlines()
    new_lines = []
    replaced = False
    for line in lines:
        if line.startswith("DATABASE_URL="):
            old_url = line.split("=", 1)[1]
            if old_url.strip() and old_url.strip() != database_url:
                ans = input(f"\n.env 里已有 DATABASE_URL=\n  {old_url}\n覆盖吗? [y/N]: ").strip().lower()
                if ans != "y":
                    print("  · 保留原 DATABASE_URL")
                    new_lines.append(line)
                    replaced = True
                    continue
            new_lines.append(f"DATABASE_URL={database_url}")
            replaced = True
        else:
            new_lines.append(line)
    if not replaced:
        new_lines.append(f"DATABASE_URL={database_url}")

    ENV_FILE.write_text("\n".join(new_lines) + "\n", encoding="utf-8")
    print(f"  · 写入 .env -> DATABASE_URL")


def run_alembic_upgrade() -> bool:
    print("\n[3/3] 跑 alembic upgrade head")
    try:
        subprocess.run(
            [sys.executable, "-m", "alembic", "upgrade", "head"],
            cwd=ROOT,
            check=True,
        )
        return True
    except subprocess.CalledProcessError as e:
        print(f"❌ alembic 失败: {e}")
        return False


def main() -> int:
    print("=" * 60)
    print("PostgreSQL 初始化脚本 — shaguabijia-app-server")
    print("=" * 60)

    host = env_or_input("PG_HOST", "PG host", "localhost")
    port = env_or_input("PG_PORT", "PG port", "5432")
    super_user = env_or_input("PG_SUPER_USER", "PG superuser", "postgres")
    super_pass = env_or_input("PG_SUPER_PASS", "PG superuser password", secret=True)
    db_name = env_or_input("APP_DB_NAME", "App database name", "shaguabijia")
    db_user = env_or_input("APP_DB_USER", "App database user", "shaguabijia_app")
    db_pass = os.environ.get("APP_DB_PASS", "").strip()
    if not db_pass:
        db_pass = secrets.token_urlsafe(32)
        print(f"  · 自动生成业务用户密码: {db_pass}")

    print(f"\n[1/3] 连接 PG (host={host}:{port}, user={super_user})")
    try:
        conn = psycopg.connect(
            host=host, port=int(port), user=super_user, password=super_pass,
            dbname="postgres", autocommit=True,
        )
    except psycopg.OperationalError as e:
        print(f"❌ 连不上 PG: {e}")
        print("   检查 1) PG 服务是否在跑 2) 超级用户密码是否正确 3) 端口防火墙是否放行")
        return 1

    print("\n[2/3] 建用户 + 建库 + 授权")
    with conn:
        ensure_role(conn, db_user, db_pass)
        ensure_database(conn, db_name, db_user)
        grant_all(conn, db_name, db_user)

    database_url = f"postgresql+psycopg://{db_user}:{db_pass}@{host}:{port}/{db_name}"
    write_env(database_url)

    if not run_alembic_upgrade():
        return 1

    print("\n" + "=" * 60)
    print("✅ 全部完成")
    print(f"   DATABASE_URL = {database_url}")
    print("   下一步: 启动服务 -> ./run.sh 或 uvicorn app.main:app --reload --port 8770")
    print("=" * 60)
    return 0


if __name__ == "__main__":
    sys.exit(main())
