#!/usr/bin/env python3
"""
将 InfoGenie Go 后端使用的 MySQL 表从「测试库」全量同步到「生产库」。
同步时在目标库对每张表执行：DROP TABLE IF EXISTS → 按源库 SHOW CREATE TABLE 重建 → 插入源库数据，
确保列结构与测试库一致（旧生产表结构会被覆盖）。

使用前务必备份生产库。会删除并重建生产库中同名表。

环境变量（未设置时沿用仓库内 test/test_mysql_connect.py 的约定）：
  源库：DB_SRC_HOST, DB_SRC_PORT, DB_SRC_NAME, DB_SRC_USER, DB_SRC_PASSWORD
  目标：DB_DST_HOST, DB_DST_PORT, DB_DST_NAME, DB_DST_USER, DB_DST_PASSWORD

示例（PowerShell）：
  $env:DB_DST_HOST='192.168.1.100'
  $env:DB_DST_NAME='infogenie'
  $env:DB_DST_USER='infogenie'
  $env:DB_DST_PASSWORD='***'
  python scripts/sync_mysql_test_to_prod.py --dry-run
  python scripts/sync_mysql_test_to_prod.py --execute
"""

from __future__ import annotations

import argparse
import os
import sys
from typing import Any, List, Sequence, Tuple

try:
    import pymysql
except ImportError:
    print("缺少依赖: pip install pymysql", file=sys.stderr)
    sys.exit(2)

# 与 internal/database/mysql.go AutoMigrate 一致
TABLES: Tuple[str, ...] = (
    "ai_configs",
    "site_60s_disabled",
    "site_ai_runtime",
    "site_60s_upstream",
    "site_ai_model_disabled",
    "site_feature_card_clicks",
)

DEFAULT_SRC = {
    "host": "10.1.1.100",
    "port": 3306,
    "name": "infogenie-test",
    "user": "infogenie-test",
    "password": "infogenie-test",
}  # 仅作文档占位；实际以 DB_SRC_* 为准

DEFAULT_DST = {
    "host": "192.168.1.100",
    "port": 3306,
    "name": "infogenie",
    "user": "infogenie",
    "password": "infogenie",
}


def _env_int(key: str, default: int) -> int:
    raw = os.environ.get(key)
    if raw is None or str(raw).strip() == "":
        return default
    return int(str(raw).strip())


def conn_params_src() -> dict:
    return {
        "host": os.environ.get("DB_SRC_HOST", DEFAULT_SRC["host"]),
        "port": _env_int("DB_SRC_PORT", DEFAULT_SRC["port"]),
        "database": os.environ.get("DB_SRC_NAME", DEFAULT_SRC["name"]),
        "user": os.environ.get("DB_SRC_USER", DEFAULT_SRC["user"]),
        "password": os.environ.get("DB_SRC_PASSWORD", DEFAULT_SRC["password"]),
    }


def conn_params_dst() -> dict:
    return {
        "host": os.environ.get("DB_DST_HOST", DEFAULT_DST["host"]),
        "port": _env_int("DB_DST_PORT", DEFAULT_DST["port"]),
        "database": os.environ.get("DB_DST_NAME", DEFAULT_DST["name"]),
        "user": os.environ.get("DB_DST_USER", DEFAULT_DST["user"]),
        "password": os.environ.get("DB_DST_PASSWORD", DEFAULT_DST["password"]),
    }


def open_conn(**kw: Any):
    return pymysql.connect(
        host=kw["host"],
        port=int(kw["port"]),
        user=kw["user"],
        password=kw["password"],
        database=kw["database"],
        charset="utf8mb4",
        connect_timeout=15,
        cursorclass=pymysql.cursors.Cursor,
    )


def fetch_all_rows(cur, table: str) -> Tuple[List[str], List[tuple]]:
    cur.execute(f"SELECT * FROM `{table}`")
    rows = cur.fetchall()
    cols = [d[0] for d in cur.description] if cur.description else []
    return cols, list(rows)


def table_exists(cur, table: str) -> bool:
    cur.execute("SHOW TABLES LIKE %s", (table,))
    return cur.fetchone() is not None


def recreate_table_from_source(source, target, table: str) -> None:
    """用源库 DDL 在目标库 DROP 后重建，保证列与测试库一致（覆盖旧结构）。"""
    with source.cursor() as sc:
        sc.execute(f"SHOW CREATE TABLE `{table}`")
        row = sc.fetchone()
        if not row:
            raise RuntimeError(f"源库不存在表: {table}")
        ddl = row[1]
    with target.cursor() as tc:
        tc.execute("SET FOREIGN_KEY_CHECKS=0")
        tc.execute(f"DROP TABLE IF EXISTS `{table}`")
        tc.execute(ddl)
        tc.execute("SET FOREIGN_KEY_CHECKS=1")
    target.commit()


def dry_run(src: dict, dst: dict, tables: Sequence[str]) -> None:
    s = open_conn(**src)
    d = open_conn(**dst)
    try:
        with s.cursor() as sc, d.cursor() as dc:
            for t in tables:
                sc.execute(f"SELECT COUNT(*) FROM `{t}`")
                (n_src,) = sc.fetchone()
                if not table_exists(dc, t):
                    print(f"{t}: source_rows={n_src} target_rows=(表不存在)")
                else:
                    dc.execute(f"SELECT COUNT(*) FROM `{t}`")
                    (n_dst,) = dc.fetchone()
                    print(f"{t}: source_rows={n_src} target_rows={n_dst}")
    finally:
        s.close()
        d.close()


def sync_tables(src: dict, dst: dict, tables: Sequence[str]) -> None:
    source = open_conn(**src)
    target = open_conn(**dst)
    try:
        with source.cursor() as sc:
            for table in tables:
                recreate_table_from_source(source, target, table)
                print(f"++ {table}: 目标表已按源库 DDL 重建")
                cols, rows = fetch_all_rows(sc, table)
                if not cols:
                    raise RuntimeError(f"无法读取列: {table}")
                placeholders = ",".join(["%s"] * len(cols))
                col_sql = ",".join(f"`{c}`" for c in cols)
                insert_sql = f"INSERT INTO `{table}` ({col_sql}) VALUES ({placeholders})"
                with target.cursor() as tc:
                    if rows:
                        tc.executemany(insert_sql, rows)
                target.commit()
                print(f"OK {table}: copied {len(rows)} rows")
    finally:
        source.close()
        target.close()


def main() -> None:
    ap = argparse.ArgumentParser(description="Sync InfoGenie MySQL tables from test to production.")
    ap.add_argument(
        "--execute",
        action="store_true",
        help="执行同步（默认仅打印说明；不加此参数则退出码 1）",
    )
    ap.add_argument("--dry-run", action="store_true", help="仅统计源/目标行数，不写生产库")
    ap.add_argument(
        "--tables",
        type=str,
        default="",
        help="逗号分隔表名子集；默认同步全部 GORM 表",
    )
    args = ap.parse_args()

    src = conn_params_src()
    dst = conn_params_dst()

    tables: Tuple[str, ...]
    if args.tables.strip():
        tables = tuple(x.strip() for x in args.tables.split(",") if x.strip())
        bad = [t for t in tables if t not in TABLES]
        if bad:
            print(f"未知表名（不在白名单）: {bad}", file=sys.stderr)
            sys.exit(1)
    else:
        tables = TABLES

    print(f"SOURCE: {src['user']}@{src['host']}:{src['port']}/{src['database']}")
    print(f"TARGET: {dst['user']}@{dst['host']}:{dst['port']}/{dst['database']}")
    print(f"TABLES: {', '.join(tables)}")

    if args.dry_run:
        dry_run(src, dst, tables)
        return

    if not args.execute:
        print("未执行：请加 --dry-run 查看行数，或加 --execute 在确认备份后同步。", file=sys.stderr)
        sys.exit(1)

    sync_tables(src, dst, tables)
    print("完成。")


if __name__ == "__main__":
    main()
