#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
数据库异常转换器

将 SQLAlchemy / DBAPI 原生异常转换为 FormData 业务异常。
同时提供 API 层的统一错误响应构建函数。

典型用法：
    from .db_error_handler import translate_db_error, build_error_response

    # Service 层 — 在 _execute_command / _execute_query 中
    try:
        await db.execute(...)
    except Exception as e:
        raise translate_db_error(e) from e

    # API 层 — 在路由函数中
    except FormDataException as e:
        raise build_error_response(e)
"""
from __future__ import annotations

import logging
import re
from typing import Optional

from fastapi import HTTPException
from sqlalchemy.exc import (
    DBAPIError,
    IntegrityError,
    OperationalError,
    ProgrammingError,
    DataError,
    InternalError,
    StatementError,
    TimeoutError as SATimeoutError,
)

from online_dev.form_data_manager.exceptions import (
    FormDataException,
    FormDataValidationError,
    UniqueConstraintError,
    NotNullConstraintError,
    CheckConstraintError,
    ForeignKeyConstraintError,
    DataTypeMismatchError,
    DataTooLongError,
    TableNotFoundError,
    ColumnNotFoundError,
    DatabaseConnectionError,
    ConnectionPoolExhaustedError,
    ConnectionTimeoutError,
    DeadlockError,
    LockTimeoutError,
    QueryError,
    InternalDatabaseError,
)

logger = logging.getLogger(__name__)


# =====================================================================
#  正则：提取数据库错误中的关键信息
# =====================================================================
_RE_COLUMN = re.compile(r'column\s+["\']?(\w+)["\']?', re.IGNORECASE)
_RE_TABLE = re.compile(r'(?:relation|table)\s+["\']?([\w."]+)["\']?', re.IGNORECASE)
_RE_CONSTRAINT = re.compile(r'constraint\s+["\']?(\w+)["\']?', re.IGNORECASE)
_RE_PG_DETAIL = re.compile(r'DETAIL:\s*(.*?)(?:\n|$)', re.IGNORECASE)
_RE_REFERENCED_FROM_TABLE = re.compile(
    r'referenced from table\s+"?([\w.]+)"?',
    re.IGNORECASE,
)
_RE_FK_ON_TABLE = re.compile(
    r'foreign key constraint\s+"?(\w+)"?\s+on table\s+"?([\w.]+)"?',
    re.IGNORECASE,
)
_RE_NOT_PRESENT_IN_TABLE = re.compile(
    r'not present in table\s+"?([\w.]+)"?',
    re.IGNORECASE,
)


def _extract_column(msg: str) -> str:
    m = _RE_COLUMN.search(msg)
    return m.group(1) if m else ""


def _extract_table(msg: str) -> str:
    m = _RE_TABLE.search(msg)
    return m.group(1) if m else ""


def _extract_constraint(msg: str) -> str:
    m = _RE_CONSTRAINT.search(msg)
    return m.group(1) if m else ""


def _normalize_table_name(table: str) -> str:
    if not table:
        return ""
    return table.strip().strip('"').split(".")[-1]


def _extract_fk_referencing_table(msg: str) -> str:
    detail_match = _RE_PG_DETAIL.search(msg)
    detail = detail_match.group(1).strip() if detail_match else msg

    referenced_match = _RE_REFERENCED_FROM_TABLE.search(detail)
    if referenced_match:
        return _normalize_table_name(referenced_match.group(1))

    not_present_match = _RE_NOT_PRESENT_IN_TABLE.search(detail)
    if not_present_match:
        return _normalize_table_name(not_present_match.group(1))

    fk_on_table_match = _RE_FK_ON_TABLE.search(msg)
    if fk_on_table_match:
        return _normalize_table_name(fk_on_table_match.group(2))

    return ""


# =====================================================================
#  核心转换函数
# =====================================================================

def translate_db_error(exc: Exception) -> FormDataException:
    """
    将数据库异常转换为业务异常。

    优先使用 SQLAlchemy 异常类型进行判断，再辅以错误信息字符串匹配，
    确保对 PostgreSQL 和 MySQL 两种方言都有良好的覆盖。
    """
    msg = str(exc).lower()
    orig_msg = str(exc)

    # ------------------------------------------------------------------
    #  1. IntegrityError — 约束违反
    # SQLAlchemy 会将 asyncpg/MySQL 的约束错误包装为 IntegrityError，
    # 实际 DBAPI 异常在 exc.orig 中，类型判断必须用外层包装类。
    # ------------------------------------------------------------------
    if isinstance(exc, IntegrityError):
        return _handle_integrity_error(exc, msg, orig_msg)

    # ------------------------------------------------------------------
    #  2. DataError — 数据格式/长度问题
    # ------------------------------------------------------------------
    if isinstance(exc, DataError):
        return _handle_data_error(exc, msg, orig_msg)

    # ------------------------------------------------------------------
    #  3. OperationalError — 连接、死锁、超时等运行时问题
    # ------------------------------------------------------------------
    if isinstance(exc, OperationalError):
        return _handle_operational_error(exc, msg, orig_msg)

    # ------------------------------------------------------------------
    #  4. ProgrammingError — SQL 语法、表/列不存在
    # ------------------------------------------------------------------
    if isinstance(exc, ProgrammingError):
        return _handle_programming_error(exc, msg, orig_msg)

    # ------------------------------------------------------------------
    #  5. InternalError — 数据库内部错误（部分 MySQL 死锁也走这里）
    # ------------------------------------------------------------------
    if isinstance(exc, InternalError):
        if "deadlock" in msg:
            return DeadlockError()
        return InternalDatabaseError(detail=orig_msg)

    # ------------------------------------------------------------------
    #  6. SQLAlchemy TimeoutError — 连接池超时
    # ------------------------------------------------------------------
    if isinstance(exc, SATimeoutError):
        return ConnectionPoolExhaustedError()

    # ------------------------------------------------------------------
    #  7. MissingGreenlet（异步上下文错误）
    # ------------------------------------------------------------------
    if "missinggreenlet" in msg or "MissingGreenlet" in orig_msg:
        return InternalDatabaseError(detail="async context error (MissingGreenlet)")

    # ------------------------------------------------------------------
    #  8. 其他 DBAPIError（含 PG 事务已中止）
    # ------------------------------------------------------------------
    if isinstance(exc, DBAPIError):
        if _is_failed_transaction_error(msg):
            return _handle_failed_transaction_error(orig_msg)
        return InternalDatabaseError(detail=orig_msg)

    # ------------------------------------------------------------------
    #  9. 如果已经是业务异常，原样返回
    # ------------------------------------------------------------------
    if isinstance(exc, FormDataException):
        return exc

    # ------------------------------------------------------------------
    #  10. 兜底 — 通过字符串匹配再尝试识别一轮
    # ------------------------------------------------------------------
    return _fallback_string_match(exc, msg, orig_msg)


# =====================================================================
#  分类处理器
# =====================================================================

def _handle_integrity_error(
    exc: IntegrityError, msg: str, orig_msg: str
) -> FormDataException:
    """处理 IntegrityError 的各种子类型"""

    # --- 唯一约束 ---
    if any(kw in msg for kw in ("unique", "duplicate key", "duplicate entry", "uniqueviolation")):
        fields = _extract_unique_fields(orig_msg)
        constraint = _extract_constraint(orig_msg)
        if fields:
            labels = ", ".join(f"'{f}'" for f in fields)
            return UniqueConstraintError(
                f"以下字段的值已存在: {labels}",
                fields=fields,
                constraint_name=constraint,
            )
        return UniqueConstraintError(constraint_name=constraint)

    # --- 非空约束 ---
    if any(kw in msg for kw in ("not-null", "notnullviolation", "null value in column", "cannot be null", "doesn't have a default")):
        col = _extract_column(orig_msg)
        return NotNullConstraintError(column=col)

    # --- 外键约束 ---
    if any(
        kw in msg
        for kw in (
            "foreign key",
            "foreignkeyviolation",
            "restrictviolation",
            "a foreign key constraint fails",
            "is still referenced",
            "referenced from table",
            "restrict setting of foreign key",
        )
    ):
        return _build_foreign_key_error(orig_msg)

    # --- CHECK 约束 ---
    if any(kw in msg for kw in ("check constraint", "checkviolation", "check_violation")):
        constraint = _extract_constraint(orig_msg)
        return CheckConstraintError(constraint_name=constraint)

    # --- 无法识别的 IntegrityError ---
    return FormDataValidationError(
        "数据违反约束条件，请检查是否存在重复或无效数据",
    )


def _handle_data_error(
    exc: DataError, msg: str, orig_msg: str
) -> FormDataException:
    """处理 DataError"""

    # 数据超长
    if any(kw in msg for kw in ("too long", "value too long", "data too long", "string data, right truncation")):
        col = _extract_column(orig_msg)
        return DataTooLongError(column=col)

    # 数据类型不匹配
    if any(kw in msg for kw in (
        "invalid input syntax", "invalid text representation", "invalid input for query argument",
        "incorrect", "out of range", "numeric value", "expected str", "expected int",
    )):
        return DataTypeMismatchError(detail=orig_msg)

    return DataTypeMismatchError(detail=orig_msg)


def _handle_operational_error(
    exc: OperationalError, msg: str, orig_msg: str
) -> FormDataException:
    """处理 OperationalError"""

    # 死锁
    if any(kw in msg for kw in ("deadlock", "deadlock detected")):
        return DeadlockError()

    # 锁等待超时
    if any(kw in msg for kw in ("lock wait timeout", "lock timeout", "could not obtain lock")):
        return LockTimeoutError()

    # 连接相关
    if any(kw in msg for kw in (
        "connection refused", "connection reset",
        "connection timed out", "connection is closed",
        "server closed the connection", "broken pipe",
        "can't connect", "unable to connect",
        "lost connection", "gone away",
    )):
        return DatabaseConnectionError(detail=orig_msg)

    # 连接池超时
    if "queuepool" in msg and ("timeout" in msg or "limit" in msg):
        return ConnectionPoolExhaustedError()

    # 查询超时 (statement_timeout / max_execution_time)
    if any(kw in msg for kw in ("statement timeout", "query timeout", "max_execution_time")):
        return ConnectionTimeoutError()

    # 表不存在（MySQL 的 OperationalError 1146）
    if any(kw in msg for kw in ("doesn't exist", "does not exist", "no such table")):
        table = _extract_table(orig_msg)
        return TableNotFoundError(table=table)

    return DatabaseConnectionError(detail=orig_msg)


def _handle_programming_error(
    exc: ProgrammingError, msg: str, orig_msg: str
) -> FormDataException:
    """处理 ProgrammingError"""

    # 表不存在
    if any(kw in msg for kw in ("relation", "table")) and "does not exist" in msg:
        table = _extract_table(orig_msg)
        return TableNotFoundError(table=table)

    # 列不存在
    if any(kw in msg for kw in ("column", "field", "unknown column")) and any(
        kw in msg for kw in ("does not exist", "not found", "unknown")
    ):
        col = _extract_column(orig_msg)
        return ColumnNotFoundError(column=col)

    # SQL 语法错误
    if any(kw in msg for kw in ("syntax error", "you have an error in your sql")):
        return QueryError(detail=orig_msg)

    return QueryError(detail=orig_msg)


def _fallback_string_match(
    exc: Exception, msg: str, orig_msg: str
) -> FormDataException:
    """兜底的字符串匹配，尽可能识别常见场景"""

    if "column" in msg and "does not exist" in msg:
        col = _extract_column(orig_msg)
        return ColumnNotFoundError(column=col)

    if ("relation" in msg or "table" in msg) and "does not exist" in msg:
        table = _extract_table(orig_msg)
        return TableNotFoundError(table=table)

    if "duplicate key" in msg or "unique" in msg:
        return UniqueConstraintError()

    if "null value" in msg or "cannot be null" in msg:
        col = _extract_column(orig_msg)
        return NotNullConstraintError(column=col)

    if any(
        kw in msg
        for kw in (
            "foreign key",
            "referenced from table",
            "restrict setting of foreign key",
            "a foreign key constraint fails",
        )
    ):
        return _build_foreign_key_error(orig_msg)

    if _is_failed_transaction_error(msg):
        return _handle_failed_transaction_error(orig_msg)

    if "connection" in msg:
        return DatabaseConnectionError(detail=orig_msg)

    return InternalDatabaseError(detail=orig_msg)


def _build_foreign_key_error(orig_msg: str) -> ForeignKeyConstraintError:
    detail_match = _RE_PG_DETAIL.search(orig_msg)
    detail = detail_match.group(1).strip() if detail_match else ""
    referenced_table = _extract_fk_referencing_table(orig_msg)
    constraint = _extract_constraint(orig_msg)
    msg_lower = orig_msg.lower()

    if any(kw in msg_lower for kw in ("delete", "referenced from table", "restrict setting")):
        if referenced_table:
            return ForeignKeyConstraintError(
                message=(
                    f"无法删除，该数据已被「{referenced_table}」引用，"
                    "请先删除或解除关联数据"
                ),
                detail=detail,
                referenced_table=referenced_table,
                constraint_name=constraint,
            )
        return ForeignKeyConstraintError(
            message="无法删除，该数据已被其他数据引用，请先删除或解除关联数据",
            detail=detail,
            constraint_name=constraint,
        )

    if any(kw in msg_lower for kw in ("insert", "update")):
        if referenced_table:
            return ForeignKeyConstraintError(
                message=f"操作失败，引用的「{referenced_table}」数据不存在或无效",
                detail=detail,
                referenced_table=referenced_table,
                constraint_name=constraint,
            )
        return ForeignKeyConstraintError(
            message="操作失败，引用的关联数据不存在或无效",
            detail=detail,
            constraint_name=constraint,
        )

    return ForeignKeyConstraintError(
        detail=detail,
        referenced_table=referenced_table,
        constraint_name=constraint,
    )


def _is_failed_transaction_error(msg: str) -> bool:
    return any(
        kw in msg
        for kw in (
            "infailedsqltransaction",
            "current transaction is aborted",
            "commands ignored until end of transaction block",
        )
    )


def is_aborted_transaction_error(exc: Exception) -> bool:
    """判断异常是否由 PostgreSQL 已中止事务引起。"""
    messages = [str(exc).lower()]
    orig = getattr(exc, "orig", None)
    if orig is not None:
        messages.append(str(orig).lower())
    return any(_is_failed_transaction_error(msg) for msg in messages)


def _handle_failed_transaction_error(orig_msg: str) -> FormDataException:
    """事务已失败后再次执行 SQL 的兜底处理，尽量保留原始约束错误语义。"""
    if any(
        kw in orig_msg.lower()
        for kw in (
            "foreign key",
            "referenced from table",
            "restrict setting of foreign key",
            "a foreign key constraint fails",
        )
    ):
        return _build_foreign_key_error(orig_msg)

    logger.error("Database operation attempted inside failed transaction: %s", orig_msg)
    return InternalDatabaseError(
        detail="transaction aborted before error could be translated"
    )


# =====================================================================
#  辅助函数
# =====================================================================

def _extract_unique_fields(msg: str) -> list[str]:
    """
    从唯一约束错误中尝试提取冲突的字段名。

    PostgreSQL DETAIL:  Key (email)=(xxx) already exists.
    MySQL:              Duplicate entry 'xxx' for key 'uq_email'
    """
    # PostgreSQL: Key (col1, col2)=(...) already exists
    pg_match = re.search(r'Key\s*\(([^)]+)\)', msg)
    if pg_match:
        return [f.strip().strip('"') for f in pg_match.group(1).split(",")]

    # MySQL: for key 'index_name'
    mysql_match = re.search(r"for key\s+'(\w+)'", msg, re.IGNORECASE)
    if mysql_match:
        return [mysql_match.group(1)]

    return []


# =====================================================================
#  API 层：统一错误响应
# =====================================================================

def build_error_response(exc: FormDataException) -> HTTPException:
    """
    将 FormDataException 转换为 FastAPI HTTPException。

    响应 body 结构：
    {
        "detail": "用户友好的错误信息",
        "error_code": "UNIQUE_CONSTRAINT_VIOLATION",
        "context": { ... }       // 可选的结构化上下文
    }
    """
    return HTTPException(
        status_code=exc.http_status,
        detail={
            "message": str(exc),
            "error_code": exc.error_code,
            "context": exc.context,
        },
    )


def format_error_message(exc: Exception, *, max_length: int = 300) -> str:
    """将异常转为面向用户的简短错误信息（用于导入行级错误等场景）"""
    if isinstance(exc, FormDataException):
        return str(exc)[:max_length]
    return str(translate_db_error(exc))[:max_length]


def _log_form_data_exception(exc: FormDataException) -> None:
    """记录表单数据业务异常的完整信息（含 context 中的原始错误详情，不截断）"""
    log_fn = logger.error if exc.http_status >= 500 else logger.warning
    log_fn("表单数据业务异常: [%s] %s", exc.error_code, exc)

    if not exc.context:
        return

    if detail := exc.context.get("detail"):
        log_fn("原始错误详情:\n%s", detail)

    other = {k: v for k, v in exc.context.items() if k != "detail" and v not in (None, "", [], {})}
    if other:
        log_fn("异常上下文: %s", other)


def handle_db_error(e: Exception) -> HTTPException:
    """
    API 层的统一入口：先转换为业务异常，再构建 HTTP 响应。

    用法：
        except Exception as e:
            raise handle_db_error(e)
    """
    if isinstance(e, FormDataException):
        _log_form_data_exception(e)
        return build_error_response(e)

    logger.error("数据库操作错误: %s", e, exc_info=True)
    biz_exc = translate_db_error(e)
    _log_form_data_exception(biz_exc)
    return build_error_response(biz_exc)
