"""
异常处理工具的单元测试

测试异常类、装饰器和上下文管理器的功能。
"""

import pytest
import logging

from src.symphra_excel.utils.exceptions import (
    # 异常类
    SymphraExcelError,
    FileFormatError,
    TemplateError,
    StyleError,
    ValidationError,
    MemoryError,
    AsyncError,
    ImageError,
    WorksheetError,
    # 装饰器
    handle_exceptions,
    handle_validation_errors,
    handle_file_errors,
    handle_style_errors,
    # 上下文管理器
    error_context,
    file_operation_context,
    resource_cleanup_context,
    # 工具函数
    get_error_chain,
    format_error_chain,
    log_exception,
)


# ==================== 异常类测试 ====================


class TestExceptionClasses:
    """测试异常类"""

    def test_base_exception(self):
        """测试基础异常类"""
        exc = SymphraExcelError("测试错误")
        assert str(exc) == "测试错误"
        assert exc.message == "测试错误"
        assert exc.details is None

    def test_exception_with_details(self):
        """测试带详细信息的异常"""
        exc = SymphraExcelError("测试错误", details={"key": "value"})
        assert "测试错误" in str(exc)
        assert "详细信息" in str(exc)
        assert exc.details == {"key": "value"}

    def test_validation_error(self):
        """测试验证错误"""
        exc = ValidationError("验证失败")
        assert isinstance(exc, SymphraExcelError)
        assert str(exc) == "验证失败"

    def test_file_format_error(self):
        """测试文件格式错误"""
        exc = FileFormatError("文件格式错误")
        assert isinstance(exc, SymphraExcelError)

    def test_all_exception_types(self):
        """测试所有异常类型"""
        exceptions = [
            FileFormatError,
            TemplateError,
            StyleError,
            ValidationError,
            MemoryError,
            AsyncError,
            ImageError,
            WorksheetError,
        ]

        for exc_class in exceptions:
            exc = exc_class("测试")
            assert isinstance(exc, SymphraExcelError)
            assert isinstance(exc, Exception)


# ==================== 装饰器测试 ====================


class TestExceptionHandlingDecorators:
    """测试异常处理装饰器"""

    def test_handle_exceptions_basic(self):
        """测试基本异常处理"""

        @handle_exceptions(error_type=ValidationError, error_message="操作失败")
        def failing_function():
            raise ValueError("原始错误")

        with pytest.raises(ValidationError, match="操作失败"):
            failing_function()

    def test_handle_exceptions_reraise_false(self):
        """测试不重新抛出异常"""

        @handle_exceptions(
            error_type=ValidationError,
            error_message="操作失败",
            reraise=False,
            default_return="默认值",
        )
        def failing_function():
            raise ValueError("原始错误")

        result = failing_function()
        assert result == "默认值"

    def test_handle_exceptions_passthrough(self):
        """测试已有异常直接传递"""

        @handle_exceptions(error_type=ValidationError)
        def failing_function():
            raise FileFormatError("文件错误")

        with pytest.raises(FileFormatError, match="文件错误"):
            failing_function()

    def test_handle_exceptions_successful(self):
        """测试成功执行"""

        @handle_exceptions(error_type=ValidationError)
        def successful_function():
            return "成功"

        result = successful_function()
        assert result == "成功"

    def test_handle_validation_errors(self):
        """测试验证错误装饰器"""

        @handle_validation_errors(error_message="数据验证失败")
        def validate_data(value):
            if value < 0:
                raise ValueError("值不能为负数")
            return value

        assert validate_data(10) == 10

        with pytest.raises(ValidationError, match="数据验证失败"):
            validate_data(-1)

    def test_handle_file_errors(self):
        """测试文件错误装饰器"""

        @handle_file_errors(error_message="读取文件失败")
        def read_file():
            raise IOError("文件不存在")

        with pytest.raises(FileFormatError, match="读取文件失败"):
            read_file()

    def test_handle_style_errors(self):
        """测试样式错误装饰器"""

        @handle_style_errors(error_message="应用样式失败")
        def apply_style():
            raise ValueError("无效的样式")

        with pytest.raises(StyleError, match="应用样式失败"):
            apply_style()


# ==================== 上下文管理器测试 ====================


class TestContextManagers:
    """测试上下文管理器"""

    def test_error_context_basic(self):
        """测试基本错误上下文"""
        with pytest.raises(ValidationError, match="验证数据失败"):
            with error_context("验证数据", error_type=ValidationError):
                raise ValueError("无效的值")

    def test_error_context_successful(self):
        """测试成功执行的上下文"""
        with error_context("操作"):
            result = 1 + 1

        assert result == 2

    def test_error_context_with_cleanup(self):
        """测试带清理的错误上下文"""
        cleanup_called = []

        def cleanup():
            cleanup_called.append(True)

        with pytest.raises(ValidationError):
            with error_context("操作", error_type=ValidationError, cleanup=cleanup):
                raise ValueError("错误")

        assert len(cleanup_called) == 1

    def test_error_context_cleanup_exception(self):
        """测试清理函数异常"""

        def failing_cleanup():
            raise RuntimeError("清理失败")

        # 清理失败不应影响主异常
        with pytest.raises(ValidationError):
            with error_context(
                "操作", error_type=ValidationError, cleanup=failing_cleanup
            ):
                raise ValueError("错误")

    def test_file_operation_context(self):
        """测试文件操作上下文"""
        with pytest.raises(FileFormatError, match="读取 'test.xlsx'"):
            with file_operation_context("test.xlsx", operation="读取"):
                raise IOError("文件不存在")

    def test_resource_cleanup_context(self):
        """测试资源清理上下文"""

        class MockResource:
            def __init__(self):
                self.closed = False

            def close(self):
                self.closed = True

        resource1 = MockResource()
        resource2 = MockResource()

        try:
            with resource_cleanup_context(resource1, resource2, operation="处理"):
                raise ValueError("错误")
        except SymphraExcelError:
            pass

        assert resource1.closed
        assert resource2.closed

    def test_resource_cleanup_successful(self):
        """测试资源清理成功情况"""

        class MockResource:
            def __init__(self):
                self.closed = False

            def close(self):
                self.closed = True

        resource = MockResource()

        with resource_cleanup_context(resource, operation="处理"):
            pass

        assert resource.closed

    def test_resource_cleanup_with_cleanup_method(self):
        """测试使用cleanup方法的资源"""

        class MockResource:
            def __init__(self):
                self.cleaned = False

            def cleanup(self):
                self.cleaned = True

        resource = MockResource()

        with resource_cleanup_context(resource):
            pass

        assert resource.cleaned


# ==================== 工具函数测试 ====================


class TestUtilityFunctions:
    """测试工具函数"""

    def test_get_error_chain_single(self):
        """测试单层异常链"""
        exc = ValueError("错误")
        chain = get_error_chain(exc)

        assert len(chain) == 1
        assert "错误" in chain[0]

    def test_get_error_chain_nested(self):
        """测试嵌套异常链"""
        try:
            try:
                raise ValueError("原始错误")
            except ValueError as e:
                raise ValidationError("包装错误") from e
        except ValidationError as e:
            chain = get_error_chain(e)

            assert len(chain) == 2
            assert "包装错误" in chain[0]
            assert "原始错误" in chain[1]

    def test_format_error_chain_single(self):
        """测试格式化单层异常"""
        exc = ValueError("错误")
        formatted = format_error_chain(exc)

        assert "错误: 错误" in formatted

    def test_format_error_chain_nested(self):
        """测试格式化嵌套异常"""
        try:
            try:
                raise ValueError("原始错误")
            except ValueError as e:
                raise ValidationError("包装错误") from e
        except ValidationError as e:
            formatted = format_error_chain(e)

            assert "错误: 包装错误" in formatted
            assert "原因: 原始错误" in formatted

    def test_format_error_chain_custom_indent(self):
        """测试自定义缩进"""
        try:
            try:
                raise ValueError("原始错误")
            except ValueError as e:
                raise ValidationError("包装错误") from e
        except ValidationError as e:
            formatted = format_error_chain(e, indent="    ")

            assert "    原因:" in formatted

    def test_log_exception(self, caplog):
        """测试记录异常日志"""
        exc = ValidationError("测试错误")

        with caplog.at_level(logging.ERROR):
            log_exception(exc, include_traceback=False)

        assert "测试错误" in caplog.text

    def test_log_exception_with_chain(self, caplog):
        """测试记录异常链日志"""
        try:
            try:
                raise ValueError("原始错误")
            except ValueError as e:
                raise ValidationError("包装错误") from e
        except ValidationError as exc:
            with caplog.at_level(logging.ERROR):
                log_exception(exc, include_traceback=False)

            assert "包装错误" in caplog.text
            assert "原始错误" in caplog.text


# ==================== 集成测试 ====================


class TestIntegration:
    """集成测试"""

    def test_decorator_with_context(self):
        """测试装饰器与上下文管理器组合使用"""

        @handle_validation_errors(error_message="验证失败")
        def validate_with_context(value):
            with error_context("检查值", error_type=ValidationError):
                if value < 0:
                    raise ValueError("值不能为负数")
            return value

        assert validate_with_context(10) == 10

        with pytest.raises(ValidationError):
            validate_with_context(-1)

    def test_nested_contexts(self):
        """测试嵌套上下文"""
        with pytest.raises(SymphraExcelError):  # 内层抛出基础异常
            with error_context("外层操作", error_type=ValidationError):
                with error_context("内层操作"):
                    raise ValueError("错误")

    def test_complex_error_chain(self):
        """测试复杂异常链"""
        try:
            try:
                try:
                    raise ValueError("原始错误")
                except ValueError as e:
                    raise RuntimeError("中间错误") from e
            except RuntimeError as e:
                raise ValidationError("最终错误") from e
        except ValidationError as e:
            chain = get_error_chain(e)
            assert len(chain) >= 2
            formatted = format_error_chain(e)
            assert "最终错误" in formatted


# ==================== 边界条件测试 ====================


class TestEdgeCases:
    """测试边界条件"""

    def test_empty_error_message(self):
        """测试空错误消息"""
        exc = SymphraExcelError("")
        assert str(exc) == ""

    def test_unicode_error_message(self):
        """测试Unicode错误消息"""
        exc = SymphraExcelError("错误：文件 '测试.xlsx' 不存在")
        assert "测试.xlsx" in str(exc)

    def test_very_long_error_message(self):
        """测试超长错误消息"""
        long_msg = "错误" * 1000
        exc = SymphraExcelError(long_msg)
        assert len(str(exc)) > 1000

    def test_exception_with_none_details(self):
        """测试None详细信息"""
        exc = SymphraExcelError("错误", details=None)
        assert str(exc) == "错误"

    def test_exception_with_complex_details(self):
        """测试复杂详细信息"""
        details = {"key": "value", "list": [1, 2, 3], "nested": {"a": 1}}
        exc = SymphraExcelError("错误", details=details)
        assert exc.details == details


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
