#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""外部数据库连接池（按连接目标复用）"""
import asyncio
import hashlib
import logging
from typing import Any, Dict, Optional

logger = logging.getLogger(__name__)

_POOL_MAX_SIZE = 5
_pool_lock = asyncio.Lock()


def _pool_identity(user: str, password: str) -> str:
    """生成连接池身份标识（含密码摘要，避免换密后复用旧池）"""
    digest = hashlib.sha256(f"{user or ''}\0{password or ''}".encode()).hexdigest()[:12]
    return f"{user or '-'}#{digest}"


_pg_pool_cache: Dict[str, Any] = {}
_mysql_pool_cache: Dict[str, Any] = {}
_mssql_pool_cache: Dict[str, Any] = {}
_oracle_pool_cache: Dict[str, Any] = {}


async def get_asyncpg_pool(
    host: str,
    port: int,
    user: str,
    password: str,
    database: str,
):
    import asyncpg

    db_name = database or "postgres"
    key = f"pg://{_pool_identity(user, password)}@{host}:{port}/{db_name}"
    async with _pool_lock:
        pool = _pg_pool_cache.get(key)
        if pool is None or getattr(pool, "_closed", False):
            pool = await asyncpg.create_pool(
                host=host,
                port=port,
                user=user,
                password=password,
                database=db_name,
                min_size=0,
                max_size=_POOL_MAX_SIZE,
                timeout=10,
                command_timeout=60,
            )
            _pg_pool_cache[key] = pool
            logger.debug("Created asyncpg pool: %s (max=%s)", key, _POOL_MAX_SIZE)
        return pool


async def get_aiomysql_pool(
    host: str,
    port: int,
    user: str,
    password: str,
    database: str,
):
    import aiomysql

    db_name = database or ""
    key = f"mysql://{_pool_identity(user, password)}@{host}:{port}/{db_name}"
    async with _pool_lock:
        pool = _mysql_pool_cache.get(key)
        if pool is None or pool.closed:
            pool = await aiomysql.create_pool(
                host=host,
                port=port,
                user=user,
                password=password,
                db=db_name,
                charset="utf8mb4",
                autocommit=True,
                minsize=0,
                maxsize=_POOL_MAX_SIZE,
                connect_timeout=10,
            )
            _mysql_pool_cache[key] = pool
            logger.debug("Created aiomysql pool: %s (max=%s)", key, _POOL_MAX_SIZE)
        return pool


def build_mssql_dsn(
    host: str,
    port: int,
    user: str,
    password: str,
    database: str,
    extra_options: Optional[Dict[str, Any]] = None,
) -> str:
    opts = extra_options or {}
    driver = opts.get("odbc_driver", "ODBC Driver 18 for SQL Server")
    db_name = database or "master"
    encrypt = opts.get("encrypt", "yes")
    trust = opts.get("trust_server_certificate", "yes")
    return (
        f"DRIVER={{{driver}}};"
        f"SERVER={host},{port};"
        f"DATABASE={db_name};"
        f"UID={user};"
        f"PWD={password};"
        f"Encrypt={encrypt};"
        f"TrustServerCertificate={trust};"
    )


async def get_aioodbc_pool(
    host: str,
    port: int,
    user: str,
    password: str,
    database: str,
    extra_options: Optional[Dict[str, Any]] = None,
):
    import aioodbc

    db_name = database or "master"
    key = f"mssql://{_pool_identity(user, password)}@{host}:{port}/{db_name}"
    async with _pool_lock:
        pool = _mssql_pool_cache.get(key)
        if pool is None or pool.closed:
            dsn = build_mssql_dsn(host, port, user, password, db_name, extra_options)
            pool = await aioodbc.create_pool(
                dsn=dsn,
                minsize=0,
                maxsize=_POOL_MAX_SIZE,
                autocommit=True,
            )
            _mssql_pool_cache[key] = pool
            logger.debug("Created aioodbc pool: %s (max=%s)", key, _POOL_MAX_SIZE)
        return pool


def build_oracle_dsn(host: str, port: int, service_name: str) -> str:
    import oracledb

    service = service_name or "ORCL"
    return oracledb.make_dsn(host, port, service_name=service)


async def get_oracledb_pool(
    host: str,
    port: int,
    user: str,
    password: str,
    database: str,
    extra_options: Optional[Dict[str, Any]] = None,
):
    import oracledb

    service = database or (extra_options or {}).get("service_name") or "ORCL"
    key = f"oracle://{_pool_identity(user, password)}@{host}:{port}/{service}"
    async with _pool_lock:
        pool = _oracle_pool_cache.get(key)
        if pool is None or (hasattr(pool, "opened") and not pool.opened):
            dsn = build_oracle_dsn(host, port, service)
            pool = oracledb.create_pool_async(
                user=user,
                password=password,
                dsn=dsn,
                min=0,
                max=_POOL_MAX_SIZE,
            )
            await pool.open()
            _oracle_pool_cache[key] = pool
            logger.debug("Created oracledb pool: %s (max=%s)", key, _POOL_MAX_SIZE)
        return pool


async def probe_asyncpg_connection(
    host: str,
    port: int,
    user: str,
    password: str,
    database: str,
) -> None:
    """单次 PostgreSQL 探测（不走连接池，避免多 worker 下池缓存导致测试结果抖动）"""
    import asyncpg

    conn = await asyncpg.connect(
        host=host,
        port=port,
        user=user,
        password=password,
        database=database or "postgres",
        timeout=10,
    )
    try:
        await conn.execute("SELECT 1")
    finally:
        await conn.close()


async def probe_aiomysql_connection(
    host: str,
    port: int,
    user: str,
    password: str,
    database: str,
) -> None:
    """单次 MySQL 探测"""
    import aiomysql

    conn = await aiomysql.connect(
        host=host,
        port=port,
        user=user,
        password=password,
        db=database or "",
        charset="utf8mb4",
        connect_timeout=10,
    )
    try:
        async with conn.cursor() as cursor:
            await cursor.execute("SELECT 1")
    finally:
        conn.close()


async def probe_aioodbc_connection(
    host: str,
    port: int,
    user: str,
    password: str,
    database: str,
    extra_options: Optional[Dict[str, Any]] = None,
) -> None:
    """单次 SQL Server 探测"""
    import aioodbc

    dsn = build_mssql_dsn(host, port, user, password, database or "master", extra_options)
    conn = await aioodbc.connect(dsn=dsn, timeout=10)
    try:
        async with conn.cursor() as cursor:
            await cursor.execute("SELECT 1")
    finally:
        await conn.close()


async def probe_oracledb_connection(
    host: str,
    port: int,
    user: str,
    password: str,
    database: str,
    extra_options: Optional[Dict[str, Any]] = None,
) -> None:
    """单次 Oracle 探测"""
    import oracledb

    service = database or (extra_options or {}).get("service_name") or "ORCL"
    dsn = build_oracle_dsn(host, port, service)
    conn = await oracledb.connect_async(user=user, password=password, dsn=dsn)
    try:
        async with conn.cursor() as cursor:
            await cursor.execute("SELECT 1 FROM DUAL")
    finally:
        await conn.close()


async def close_all_manager_pools() -> None:
    """关闭所有外部数据库连接池（应用 shutdown 时调用）"""
    async with _pool_lock:
        for key, pool in list(_pg_pool_cache.items()):
            try:
                await pool.close()
            except Exception as exc:
                logger.warning("Failed to close asyncpg pool %s: %s", key, exc)
        _pg_pool_cache.clear()

        for key, pool in list(_mysql_pool_cache.items()):
            try:
                pool.close()
                await pool.wait_closed()
            except Exception as exc:
                logger.warning("Failed to close aiomysql pool %s: %s", key, exc)
        _mysql_pool_cache.clear()

        for key, pool in list(_mssql_pool_cache.items()):
            try:
                pool.close()
                await pool.wait_closed()
            except Exception as exc:
                logger.warning("Failed to close aioodbc pool %s: %s", key, exc)
        _mssql_pool_cache.clear()

        for key, pool in list(_oracle_pool_cache.items()):
            try:
                await pool.close()
            except Exception as exc:
                logger.warning("Failed to close oracledb pool %s: %s", key, exc)
        _oracle_pool_cache.clear()
