"""
异常体系单元测试
================

验证 AppException / InsufficientPointsException 的结构化字段，
确保前端可以通过 error_code 识别错误类型。
"""

from fastapi import HTTPException

from app.core.exceptions import (
    AIEmptyResponseException,
    AIParseErrorException,
    AITimeoutException,
    AppException,
    BusinessException,
    InsufficientPointsException,
    NotFoundException,
    PromptNotFoundException,
    ValidationException,
)


class TestAppException:
    """业务异常基类"""

    def test_app_exception_has_error_code(self) -> None:
        exc = AppException(
            status_code=400,
            message="参数错误",
            error_code="validation_error",
        )
        assert exc.status_code == 400
        assert exc.message == "参数错误"
        assert exc.error_code == "validation_error"
        assert exc.detail == {"message": "参数错误", "error_code": "validation_error"}

    def test_app_exception_detail_can_be_dict(self) -> None:
        exc = AppException(
            status_code=422,
            message="字段校验失败",
            detail={"fields": {"name": "required"}},
            error_code="validation_error",
        )
        assert exc.detail == {
            "fields": {"name": "required"},
            "message": "字段校验失败",
            "error_code": "validation_error",
        }

    def test_subclasses_without_error_code(self) -> None:
        exc = NotFoundException("资源不存在")
        assert exc.status_code == 404
        assert exc.message == "资源不存在"
        assert exc.error_code is None


class TestInsufficientPointsException:
    """积分不足异常"""

    def test_default_fields(self) -> None:
        exc = InsufficientPointsException()
        assert exc.status_code == 402
        assert exc.message == "积分不足"
        assert exc.error_code == "insufficient_points"
        assert isinstance(exc, HTTPException)

    def test_custom_message(self) -> None:
        exc = InsufficientPointsException("余额不足，请先充值")
        assert exc.message == "余额不足，请先充值"
        assert exc.error_code == "insufficient_points"

    def test_detail_structure(self) -> None:
        exc = InsufficientPointsException("需要 10 积分")
        assert exc.detail == {
            "message": "需要 10 积分",
            "error_code": "insufficient_points",
        }


class TestOtherSubclasses:
    """其他常用子类"""

    def test_validation_exception(self) -> None:
        exc = ValidationException("字段缺失")
        assert exc.status_code == 422
        assert exc.message == "字段缺失"

    def test_business_exception(self) -> None:
        exc = BusinessException("业务状态不允许")
        assert exc.status_code == 400
        assert exc.message == "业务状态不允许"


class TestAIStructuredExceptions:
    """AI 相关结构化异常"""

    def test_prompt_not_found_exception(self) -> None:
        exc = PromptNotFoundException("未找到提示词")
        assert exc.status_code == 404
        assert exc.error_code == "prompt_not_found"

    def test_ai_empty_response_exception(self) -> None:
        exc = AIEmptyResponseException("AI 返回为空")
        assert exc.status_code == 500
        assert exc.error_code == "empty_result"

    def test_ai_parse_error_exception(self) -> None:
        exc = AIParseErrorException("解析失败")
        assert exc.status_code == 500
        assert exc.error_code == "parse_error"

    def test_ai_timeout_exception(self) -> None:
        exc = AITimeoutException("请求超时")
        assert exc.status_code == 504
        assert exc.error_code == "timeout"
