from contextlib import contextmanager
from urllib.parse import urlparse, parse_qs

import pymysql
from dbutils.pooled_db import PooledDB
from loguru import logger
from pymysql.cursors import DictCursor


class MysqlClient:
    """MySQL客户端"""

    def __init__(
            self,
            host="localhost",
            port=3306,
            user="root",
            password: str = None,
            database: str = None,
            charset="utf8mb4",
            **pool_kwargs,
    ):
        self.config = {
            "host": host,
            "port": port,
            "user": user,
            "password": password,
            "database": database,
            "charset": charset,
        }

        # 默认连接池配置
        self.pool_config = {
            "creator": pymysql,
            "autocommit": True,
            "cursorclass": DictCursor,
            "mincached": 1,
            "maxcached": 20,
            "maxshared": 10,
            "maxconnections": 100,
            "blocking": True,
            "maxusage": 0,
            "setsession": [],
            "ping": 1,
            **pool_kwargs,  # 允许自定义连接池参数
        }

        self.pool: PooledDB = None
        self._init()

    @classmethod
    def from_url(cls, url: str, **pool_kwargs):
        """从URL创建MySQL客户端实例

        Args:
            url: MySQL连接URL，格式如: mysql://user:password@host:port/database
            **pool_kwargs: 连接池配置参数

        Returns:
            MySQLClient: MySQL客户端实例
        """
        parsed = urlparse(url)

        if parsed.scheme != "mysql":
            raise ValueError("URL必须以mysql://开头")

        # 解析主机和端口
        host = parsed.hostname
        port = parsed.port or 3306
        user = parsed.username
        password = parsed.password
        database = parsed.path.lstrip("/")

        # 解析查询参数
        query_params = parse_qs(parsed.query)

        # 处理charset参数
        charset = "utf8mb4"
        if "charset" in query_params:
            charset = query_params["charset"][0]

        # 其他查询参数可以作为连接池参数
        for key, values in query_params.items():
            if key not in ["charset"]:
                value = values[0]
                # 尝试转换为整数，如果失败则保持字符串
                try:
                    if key in [
                        "mincached",
                        "maxcached",
                        "maxshared",
                        "maxconnections",
                        "maxusage",
                        "ping",
                    ]:
                        pool_kwargs[key] = int(value)
                    elif key in ["blocking"]:
                        pool_kwargs[key] = value.lower() in ["true", "1", "yes"]
                    else:
                        pool_kwargs[key] = value
                except ValueError:
                    pool_kwargs[key] = value

        return cls(
            host=host,
            port=port,
            user=user,
            password=password,
            database=database,
            charset=charset,
            **pool_kwargs,
        )

    def _init(self):
        """初始化数据库连接池"""
        config = self.pool_config | self.config
        try:
            self.pool = PooledDB(**config)
            logger.info(
                f"数据库连接池初始化成功: {self.config['host']}:{self.config['port']}/{self.config['database']}"
            )
        except Exception as e:
            logger.error(f"数据库连接池初始化失败: {e}")
            raise

    @contextmanager
    def get_connection(self):
        """获取数据库连接（上下文管理器）"""
        conn = None
        try:
            conn = self.pool.connection()
            yield conn
        except Exception as e:
            if conn:
                conn.rollback()
            logger.error(f"数据库操作异常: {e}")
            raise
        finally:
            if conn:
                conn.close()

    def _check_sql(self, sql: str):
        if not sql or not sql.strip():
            raise ValueError("SQL语句不能为空")

    def _check_table(self, table: str):
        if not table or not table.strip():
            raise ValueError("表名不能为空")

    def _build_insert_sql(self, table: str, item: dict) -> tuple[str, tuple]:
        columns = list(item.keys())
        sql = f"INSERT INTO `{table}` ({', '.join(f'`{col}`' for col in columns)}) VALUES ({', '.join(['%s'] * len(columns))})"
        return sql, tuple(item.values())

    def execute(self, sql: str, args: tuple | list | None = None) -> int:
        """执行SQL语句"""
        self._check_sql(sql)

        with self.get_connection() as conn:
            with conn.cursor() as cursor:
                affected_rows = cursor.execute(sql, args)
                return affected_rows

    def fetchone(self, sql: str, args: tuple = None) -> dict:
        """获取单条记录"""
        self._check_sql(sql)

        with self.get_connection() as conn:
            with conn.cursor() as cursor:
                cursor.execute(sql, args)
                return cursor.fetchone() or {}

    def fetchall(self, sql: str, args: tuple = None) -> list[dict]:
        """获取所有记录"""
        self._check_sql(sql)

        with self.get_connection() as conn:
            with conn.cursor() as cursor:
                cursor.execute(sql, args)
                return cursor.fetchall() or []

    def fetchmany(self, sql: str, args: tuple = None, n=2) -> list[dict]:
        """获取多条记录"""
        self._check_sql(sql)

        with self.get_connection() as conn:
            with conn.cursor() as cursor:
                cursor.execute(sql, args)
                return cursor.fetchmany(n) or []

    def insert_one(self, table: str, item: dict) -> int:
        """插入单条记录"""
        if not item:
            logger.warning("插入数据为空")
            return 0

        self._check_table(table)

        try:
            sql, values = self._build_insert_sql(table, item)
            return self.execute(sql, values)
        except Exception as e:
            logger.error(f"插入单条记录失败: {e}")
            raise

    def insert_many(self, table: str, items: list[dict]) -> int:
        """批量插入记录"""
        if not items:
            logger.warning("批量插入数据为空")
            return 0

        self._check_table(table)

        try:
            sql, _ = self._build_insert_sql(table, items[0])
            values_list = [tuple(data[col] for col in items[0].keys()) for data in items]

            with self.get_connection() as conn:
                with conn.cursor() as cursor:
                    affected_rows = cursor.executemany(sql, values_list)
                    return affected_rows
        except Exception as e:
            logger.error(f"批量插入记录失败: {e}")
            raise

    def update(
            self,
            table: str,
            item: dict,
            where: str,
            args: tuple | list | None = None,
    ) -> int:
        """更新记录"""
        if not item:
            logger.warning("更新数据为空")
            return 0

        self._check_table(table)

        if not where or not where.strip():
            raise ValueError("WHERE条件不能为空")

        try:
            set_clauses = [f"`{col}` = %s" for col in item]
            values = list(item.values())

            sql = f"UPDATE `{table}` SET {', '.join(set_clauses)} WHERE {where}"

            if args:
                values.extend(args if isinstance(args, (list, tuple)) else [args])

            return self.execute(sql, tuple(values))
        except Exception as e:
            logger.error(f"更新记录失败: {e}")
            raise

    def delete(self, table: str, where: str, args: tuple | list | None = None) -> int:
        """删除记录"""
        self._check_table(table)

        if not where or not where.strip():
            raise ValueError("WHERE条件不能为空")

        try:
            sql = f"DELETE FROM `{table}` WHERE {where}"
            return self.execute(sql, args)
        except Exception as e:
            logger.error(f"删除记录失败: {e}")
            raise

    def query(self, sql: str, args: tuple | list | None = None) -> list[dict]:
        """查询方法"""
        return self.fetchall(sql, args)

    def get_pool_status(self) -> dict:
        """获取连接池状态"""
        if self.pool:
            return {
                "min_cached": self.pool_config.get("mincached", 0),
                "max_cached": self.pool_config.get("maxcached", 0),
                "max_connections": self.pool_config.get("maxconnections", 0),
                "current_connections": (
                    len(self.pool._idle_cache)
                    if hasattr(self.pool, "_idle_cache")
                    else 0
                ),
            }
        return {}

    def close(self):
        """关闭连接池"""
        if self.pool:
            try:
                self.pool.close()
                logger.info("数据库连接池已关闭")
            except Exception as e:
                logger.error(f"关闭数据库连接池时出错: {e}")
        else:
            logger.warning("连接池未初始化或已关闭")

    def __enter__(self):
        """上下文管理器入口"""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器出口"""
        self.close()

    def __del__(self):
        """析构函数"""
        self.close()

    def create_table(self, name: str, fields: list[str], gen_id=False):
        """创建数据表

        Args:
            name: 表名
            fields: 字段列表，每个字段名，默认类型为varchar(255)
            gen_id: 是否生成自增ID字段
        """
        self._check_table(name)

        if not fields or not all(isinstance(f, str) and f.strip() for f in fields):
            raise ValueError("字段列表不能为空且必须为字符串")

        try:
            field_definitions = [f"`{field.strip()}` VARCHAR(255)" for field in fields]

            if gen_id:
                field_definitions.insert(0, "`id` INT AUTO_INCREMENT PRIMARY KEY")

            sql = f"CREATE TABLE IF NOT EXISTS `{name}` ({', '.join(field_definitions)}) ENGINE=InnoDB DEFAULT CHARSET={self.config.get('charset', 'utf8mb4')}"

            self.execute(sql)
            logger.info(f"数据表 `{name}` 创建成功或已存在")
        except Exception as e:
            logger.error(f"创建数据表 `{name}` 失败: {e}")
            raise


if __name__ == "__main__":
    db = MysqlClient.from_url("mysql://root:admin0@localhost:3306/test")
    db.create_table("test_911", ["name", "email"])
    r = db.insert_one("test_911", {"name": "wauo", "email": "wauo@qq.com"})
    print(r)
    print(db.fetchall("SELECT * FROM test_911"))
