"""将第一阶段持久化数据幂等继承到第二阶段数据库。"""

from dataclasses import dataclass

import pymysql
from pymysql.cursors import Cursor

from zbt.commands.build_analytics import build_analytics_foundation
from zbt.core.config import Settings

DATABASE_PAIRS = (
    ("insurance_s1_core", "insurance_s2_core"),
    ("insurance_s1_agent", "insurance_s2_agent"),
    ("insurance_s1_analytics", "insurance_s2_analytics"),
)

EXCLUDED_TABLES = {
    "alembic_version",
    "async_tasks",
    "auth_sessions",
    "outbox_events",
    "analytics_sync_offsets",
}

SPECIAL_RELATION_TABLES = {
    "admin_user_roles",
    "role_permissions",
}


@dataclass(frozen=True, slots=True)
class TableCopyResult:
    source_database: str
    target_database: str
    table_name: str
    source_rows: int
    target_rows_before: int
    target_rows_after: int


def quote_identifier(identifier: str) -> str:
    if not identifier.replace("_", "").isalnum():
        raise ValueError(f"不安全的数据库标识符：{identifier}")
    return f"`{identifier}`"


def build_upsert_sql(
    source_database: str,
    target_database: str,
    table_name: str,
    columns: list[str],
    primary_keys: list[str],
) -> str:
    if not columns:
        raise ValueError(f"表 {table_name} 没有可复制字段。")
    if not primary_keys:
        raise ValueError(f"表 {table_name} 没有主键，无法执行幂等继承。")

    quoted_columns = ", ".join(quote_identifier(column) for column in columns)
    update_columns = [column for column in columns if column not in primary_keys]
    if update_columns:
        update_clause = ", ".join(
            f"{quote_identifier(column)} = VALUES({quote_identifier(column)})"
            for column in update_columns
        )
    else:
        first_key = quote_identifier(primary_keys[0])
        update_clause = f"{first_key} = VALUES({first_key})"

    return (
        f"INSERT INTO {quote_identifier(target_database)}.{quote_identifier(table_name)} "
        f"({quoted_columns}) "
        f"SELECT {quoted_columns} "
        f"FROM {quote_identifier(source_database)}.{quote_identifier(table_name)} "
        f"ON DUPLICATE KEY UPDATE {update_clause}"
    )


def _table_names(cursor: Cursor, database: str) -> set[str]:
    cursor.execute(
        """
        SELECT table_name
        FROM information_schema.tables
        WHERE table_schema = %s
          AND table_type = 'BASE TABLE'
        """,
        (database,),
    )
    return {str(row[0]) for row in cursor.fetchall()}


def _column_names(cursor: Cursor, database: str, table_name: str) -> list[str]:
    cursor.execute(
        """
        SELECT column_name
        FROM information_schema.columns
        WHERE table_schema = %s
          AND table_name = %s
        ORDER BY ordinal_position
        """,
        (database, table_name),
    )
    return [str(row[0]) for row in cursor.fetchall()]


def _primary_keys(cursor: Cursor, database: str, table_name: str) -> list[str]:
    cursor.execute(
        """
        SELECT column_name
        FROM information_schema.key_column_usage
        WHERE table_schema = %s
          AND table_name = %s
          AND constraint_name = 'PRIMARY'
        ORDER BY ordinal_position
        """,
        (database, table_name),
    )
    return [str(row[0]) for row in cursor.fetchall()]


def _row_count(cursor: Cursor, database: str, table_name: str) -> int:
    cursor.execute(
        f"SELECT COUNT(*) FROM {quote_identifier(database)}.{quote_identifier(table_name)}"
    )
    row = cursor.fetchone()
    return int(row[0]) if row is not None else 0


def _copy_identity_relations(cursor: Cursor) -> list[TableCopyResult]:
    results: list[TableCopyResult] = []

    admin_roles_before = _row_count(
        cursor,
        "insurance_s2_core",
        "admin_user_roles",
    )
    cursor.execute(
        """
        DELETE relation
        FROM insurance_s2_core.admin_user_roles AS relation
        LEFT JOIN insurance_s2_core.admin_users AS admin_user
          ON admin_user.id = relation.admin_user_id
        LEFT JOIN insurance_s2_core.roles AS role
          ON role.id = relation.role_id
        WHERE admin_user.id IS NULL OR role.id IS NULL
        """
    )
    cursor.execute(
        """
        INSERT INTO insurance_s2_core.admin_user_roles (admin_user_id, role_id)
        SELECT target_user.id, target_role.id
        FROM insurance_s1_core.admin_user_roles AS source_relation
        JOIN insurance_s1_core.admin_users AS source_user
          ON source_user.id = source_relation.admin_user_id
        JOIN insurance_s2_core.admin_users AS target_user
          ON target_user.username = source_user.username
        JOIN insurance_s1_core.roles AS source_role
          ON source_role.id = source_relation.role_id
        JOIN insurance_s2_core.roles AS target_role
          ON target_role.code = source_role.code
        ON DUPLICATE KEY UPDATE
          admin_user_id = VALUES(admin_user_id)
        """
    )
    admin_roles_after = _row_count(
        cursor,
        "insurance_s2_core",
        "admin_user_roles",
    )
    results.append(
        TableCopyResult(
            source_database="insurance_s1_core",
            target_database="insurance_s2_core",
            table_name="admin_user_roles",
            source_rows=_row_count(
                cursor,
                "insurance_s1_core",
                "admin_user_roles",
            ),
            target_rows_before=admin_roles_before,
            target_rows_after=admin_roles_after,
        )
    )

    role_permissions_before = _row_count(
        cursor,
        "insurance_s2_core",
        "role_permissions",
    )
    cursor.execute(
        """
        DELETE relation
        FROM insurance_s2_core.role_permissions AS relation
        LEFT JOIN insurance_s2_core.roles AS role
          ON role.id = relation.role_id
        LEFT JOIN insurance_s2_core.permissions AS permission
          ON permission.id = relation.permission_id
        WHERE role.id IS NULL OR permission.id IS NULL
        """
    )
    cursor.execute(
        """
        INSERT INTO insurance_s2_core.role_permissions (role_id, permission_id)
        SELECT target_role.id, target_permission.id
        FROM insurance_s1_core.role_permissions AS source_relation
        JOIN insurance_s1_core.roles AS source_role
          ON source_role.id = source_relation.role_id
        JOIN insurance_s2_core.roles AS target_role
          ON target_role.code = source_role.code
        JOIN insurance_s1_core.permissions AS source_permission
          ON source_permission.id = source_relation.permission_id
        JOIN insurance_s2_core.permissions AS target_permission
          ON target_permission.code = source_permission.code
        ON DUPLICATE KEY UPDATE
          role_id = VALUES(role_id)
        """
    )
    role_permissions_after = _row_count(
        cursor,
        "insurance_s2_core",
        "role_permissions",
    )
    results.append(
        TableCopyResult(
            source_database="insurance_s1_core",
            target_database="insurance_s2_core",
            table_name="role_permissions",
            source_rows=_row_count(
                cursor,
                "insurance_s1_core",
                "role_permissions",
            ),
            target_rows_before=role_permissions_before,
            target_rows_after=role_permissions_after,
        )
    )
    return results


def inherit_stage_one_data(settings: Settings | None = None) -> list[TableCopyResult]:
    resolved = settings or Settings()
    connection = pymysql.connect(
        host=resolved.mysql_host,
        port=resolved.mysql_port,
        user=resolved.mysql_user,
        password=resolved.mysql_password,
        database=resolved.mysql_core_database,
        charset="utf8mb4",
        autocommit=False,
    )
    results: list[TableCopyResult] = []
    try:
        with connection.cursor() as cursor:
            cursor.execute("SET FOREIGN_KEY_CHECKS = 0")
            for source_database, target_database in DATABASE_PAIRS:
                source_tables = _table_names(cursor, source_database)
                target_tables = _table_names(cursor, target_database)
                copy_tables = sorted(
                    (source_tables & target_tables)
                    - EXCLUDED_TABLES
                    - SPECIAL_RELATION_TABLES
                )
                for table_name in copy_tables:
                    source_columns = _column_names(
                        cursor,
                        source_database,
                        table_name,
                    )
                    target_columns = set(
                        _column_names(cursor, target_database, table_name)
                    )
                    common_columns = [
                        column for column in source_columns if column in target_columns
                    ]
                    primary_keys = _primary_keys(
                        cursor,
                        target_database,
                        table_name,
                    )
                    source_rows = _row_count(cursor, source_database, table_name)
                    target_rows_before = _row_count(
                        cursor,
                        target_database,
                        table_name,
                    )
                    cursor.execute(
                        build_upsert_sql(
                            source_database,
                            target_database,
                            table_name,
                            common_columns,
                            primary_keys,
                        )
                    )
                    target_rows_after = _row_count(
                        cursor,
                        target_database,
                        table_name,
                    )
                    results.append(
                        TableCopyResult(
                            source_database=source_database,
                            target_database=target_database,
                            table_name=table_name,
                            source_rows=source_rows,
                            target_rows_before=target_rows_before,
                            target_rows_after=target_rows_after,
                        )
                    )
            results.extend(_copy_identity_relations(cursor))
        connection.commit()
    except Exception:
        connection.rollback()
        raise
    finally:
        try:
            with connection.cursor() as cursor:
                cursor.execute("SET FOREIGN_KEY_CHECKS = 1")
            connection.commit()
        finally:
            connection.close()

    build_analytics_foundation(resolved)
    return results


def main() -> None:
    results = inherit_stage_one_data()
    print("第一阶段数据已继承到第二阶段：")
    for result in results:
        print(
            f"- {result.target_database}.{result.table_name}: "
            f"{result.target_rows_before} -> {result.target_rows_after} "
            f"(第一阶段 {result.source_rows})"
        )


if __name__ == "__main__":
    main()
