import pymysql
from pymysql.err import OperationalError, ProgrammingError, DataError
from typing import Optional, List, Dict, Tuple, Any, Union


class MySQLClient:
    """MySQL数据库操作封装类"""

    def __init__(
            self,
            host: str,
            port: int = 3306,
            user: str = '',
            password: str = '',
            database: str = 'mysql',
            charset: str = "utf8mb4",
            autocommit: bool = False,
            cursor_type: str = "tuple"  # 游标类型：dict(字典) / tuple(元组)
    ):
        """
        初始化数据库连接配置
        :param host: 数据库主机
        :param port: 数据库端口
        :param user: 数据库用户名
        :param password: 数据库密码
        :param database: 数据库名
        :param charset: 字符集（utf8mb4支持emoji）
        :param autocommit: 是否自动提交事务
        :param cursor_type: 游标返回类型
        """
        self.host = host
        self.port = port
        self.user = user
        self.password = password
        self.database = database
        self.charset = charset
        self.autocommit = autocommit
        self.cursor_type = cursor_type.lower()
        self._is_connected: bool = False
        # 连接和游标对象
        self.conn: Optional[pymysql.connections.Connection] = None
        self.cursor: Optional[pymysql.cursors.Cursor] = None

    def connect(self) -> None:
        """创建数据库连接"""
        try:
            self.conn = pymysql.connect(
                host=self.host,
                port=self.port,
                user=self.user,
                password=self.password,
                database=self.database,
                charset=self.charset,
                autocommit=self.autocommit,
                # 防止连接超时
                connect_timeout=3,
                read_timeout=10,
                write_timeout=10
            )

            # 设置游标类型
            if self.cursor_type == "dict":
                self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)
            else:
                self.cursor = self.conn.cursor()
            self._is_connected = True
        except (OperationalError, ProgrammingError) as e:
            raise Exception(f"数据库连接失败：{str(e)}") from e

    @property
    def is_connected(self) -> bool:
        """判断是否已连接数据库"""
        return self._is_connected

    def close(self) -> None:
        """关闭数据库连接"""
        if self.cursor:
            self.cursor.close()
        if self.conn:
            self.conn.close()
        self._is_connected = False

    def __enter__(self):
        """上下文管理器：自动连接"""
        self.connect()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器：自动关闭（异常时回滚）"""
        if exc_type:
            if self.conn:
                self.conn.rollback()
            raise Exception(f"执行SQL出错：{exc_val}") from exc_val
        self.close()

    def get_one(self, sql: str, params: Optional[Tuple[Any, ...]] = None) -> Optional[
        Union[Dict[str, Any], Tuple[Any, ...]]]:
        """
        查询单条数据
        :param sql: SQL语句（使用%s作为占位符）
        :param params: SQL参数（元组类型）
        :return: 单条结果（字典/元组），无数据返回None
        """
        try:
            self.cursor.execute(sql, params or ())
            return self.cursor.fetchone()
        except DataError as e:
            raise Exception(f"查询单条数据失败：{str(e)}") from e

    def get_all(self, sql: str, params: Optional[Tuple[Any, ...]] = None) -> List[
        Union[Dict[str, Any], Tuple[Any, ...]]]:
        """
        查询多条数据
        :param sql: SQL语句
        :param params: SQL参数
        :return: 结果列表
        """
        try:
            self.cursor.execute(sql, params or ())
            return self.cursor.fetchall()
        except DataError as e:
            raise Exception(f"查询多条数据失败：{str(e)}") from e

    def execute(self, sql: str, params: Optional[Tuple[Any, ...]] = None) -> int:
        """
        执行增/删/改操作
        :param sql: SQL语句
        :param params: SQL参数
        :return: 受影响的行数
        """
        try:
            affected_rows = self.cursor.execute(sql, params or ())
            self.conn.commit()  # 手动提交（autocommit=False时）
            return affected_rows
        except DataError as e:
            self.conn.rollback()
            raise Exception(f"执行增删改失败：{str(e)}") from e

    def executemany(self, sql: str, params_list: List[Tuple[Any, ...]]) -> int:
        """
        批量执行增/删/改操作
        :param sql: SQL语句
        :param params_list: 参数列表（二维元组）
        :return: 受影响的行数
        """
        if not params_list:
            return 0

        try:
            affected_rows = self.cursor.executemany(sql, params_list)
            self.conn.commit()
            return affected_rows
        except DataError as e:
            self.conn.rollback()
            raise Exception(f"批量执行失败：{str(e)}") from e

    def batch_execute_transaction(self, sql_list: List[Tuple[str, Optional[Tuple[Any, ...]]]]) -> bool:
        """
        事务执行多条SQL
        :param sql_list: SQL列表，格式[(sql1, params1), (sql2, params2), ...]
        :return: 执行结果（True/False）
        """
        if not sql_list:
            return False

        try:
            for sql, params in sql_list:
                self.cursor.execute(sql, params or ())
            self.conn.commit()
            return True
        except Exception as e:
            self.conn.rollback()
            raise Exception(f"事务执行失败：{str(e)}") from e

    def batch_execute(self, sql_list: List[Tuple[str, Optional[Tuple[Any, ...]]]]) -> int:
        """
        非原子性逐条执行SQL列表（取消事务）
        特性：
        1. 逐条执行，每条成功后立即提交（不回滚）；
        2. 遇到错误立即终止后续执行，抛出异常；
        3. 已成功执行的SQL不会回滚（非原子性）。
        :param sql_list: SQL列表，格式[(sql1, params1), (sql2, params2), ...]
        :return: 成功执行的SQL条数
        """
        if not sql_list:
            return 0

        success_count = 0  # 记录成功执行的条数
        try:
            for idx, (sql, params) in enumerate(sql_list, 1):
                self.execute(sql, params)
                success_count += 1
            return success_count
        except Exception as e:
            # 抛出异常时，明确告知成功执行的条数和失败的SQL位置
            raise Exception(
                f"第{success_count + 1}条SQL执行失败，已成功执行{success_count}条，终止后续执行 | 错误：{str(e)}"
            ) from e
