"""
描述:
- xfintech.connect.common.error 模块的单元测试。
- 测试所有自定义异常类的继承、实例化和使用。
"""

from __future__ import annotations

import pytest

from xfintech.connect.common.error import (
    ConnectFailedError,
    ConnectKeyError,
    ConnectRefKeyError,
)

# =============================================================================
# ConnectRefKeyError Tests
# =============================================================================


def test_connectrefkeyerror_inherits_from_keyerror():
    """测试 ConnectRefKeyError 继承自 KeyError"""
    assert issubclass(ConnectRefKeyError, KeyError)


def test_connectrefkeyerror_can_instantiate():
    """测试可以实例化 ConnectRefKeyError"""
    error = ConnectRefKeyError("Test message")
    assert isinstance(error, ConnectRefKeyError)
    assert isinstance(error, KeyError)


def test_connectrefkeyerror_can_raise():
    """测试可以抛出 ConnectRefKeyError"""
    with pytest.raises(ConnectRefKeyError):
        raise ConnectRefKeyError("Missing required key")


def test_connectrefkeyerror_message():
    """测试 ConnectRefKeyError 消息"""
    msg = "'location' is required in data"
    error = ConnectRefKeyError(msg)
    # KeyError wraps message in quotes
    assert msg in str(error)


def test_connectrefkeyerror_can_catch_as_keyerror():
    """测试可以作为 KeyError 捕获"""
    with pytest.raises(KeyError):
        raise ConnectRefKeyError("Missing key")


def test_connectrefkeyerror_empty_message():
    """测试空消息"""
    error = ConnectRefKeyError()
    assert isinstance(error, ConnectRefKeyError)


def test_connectrefkeyerror_with_multiple_args():
    """测试多参数实例化"""
    error = ConnectRefKeyError("Error", "Extra info")
    assert len(error.args) == 2


def test_connectrefkeyerror_use_case():
    """测试实际使用场景"""

    def validate_data(data):
        if "location" not in data:
            raise ConnectRefKeyError("'location' is required in data")

    with pytest.raises(ConnectRefKeyError) as excinfo:
        validate_data({"kind": "s3"})
    assert "'location' is required in data" in str(excinfo.value)


def test_connectrefkeyerror_in_dict_access():
    """测试在字典访问中使用"""

    def get_location(data):
        try:
            return data["location"]
        except KeyError:
            raise ConnectRefKeyError("location key missing")

    with pytest.raises(ConnectRefKeyError):
        get_location({})


def test_connectrefkeyerror_with_cause():
    """测试带原因链的异常"""
    original = KeyError("original")
    try:
        raise ConnectRefKeyError("wrapped") from original
    except ConnectRefKeyError as e:
        assert e.__cause__ is original


# =============================================================================
# ConnectKeyError Tests
# =============================================================================


def test_connectkeyerror_inherits_from_keyerror():
    """测试 ConnectKeyError 继承自 KeyError"""
    assert issubclass(ConnectKeyError, KeyError)


def test_connectkeyerror_can_instantiate():
    """测试可以实例化 ConnectKeyError"""
    error = ConnectKeyError("Test message")
    assert isinstance(error, ConnectKeyError)
    assert isinstance(error, KeyError)


def test_connectkeyerror_can_raise():
    """测试可以抛出 ConnectKeyError"""
    with pytest.raises(ConnectKeyError):
        raise ConnectKeyError("Invalid key")


def test_connectkeyerror_message():
    """测试 ConnectKeyError 消息"""
    msg = "location cannot be None"
    error = ConnectKeyError(msg)
    assert msg in str(error)


def test_connectkeyerror_can_catch_as_keyerror():
    """测试可以作为 KeyError 捕获"""
    with pytest.raises(KeyError):
        raise ConnectKeyError("Key error")


def test_connectkeyerror_empty_message():
    """测试空消息"""
    error = ConnectKeyError()
    assert isinstance(error, ConnectKeyError)


def test_connectkeyerror_with_multiple_args():
    """测试多参数实例化"""
    error = ConnectKeyError("Error", "Extra")
    assert len(error.args) == 2


def test_connectkeyerror_use_case():
    """测试实际使用场景"""

    def validate_location(location):
        if location is None:
            raise ConnectKeyError("location cannot be None")
        if not location:
            raise ConnectKeyError("location cannot be empty")

    with pytest.raises(ConnectKeyError) as excinfo:
        validate_location(None)
    assert "cannot be None" in str(excinfo.value)

    with pytest.raises(ConnectKeyError) as excinfo:
        validate_location("")
    assert "cannot be empty" in str(excinfo.value)


def test_connectkeyerror_in_validation():
    """测试在验证逻辑中使用"""

    def check_required_keys(data, required):
        for key in required:
            if key not in data:
                raise ConnectKeyError(f"Required key missing: {key}")

    with pytest.raises(ConnectKeyError, match="location"):
        check_required_keys({"kind": "s3"}, ["location", "kind"])


def test_connectkeyerror_with_cause():
    """测试带原因链的异常"""
    original = ValueError("Invalid value")
    try:
        raise ConnectKeyError("Key invalid") from original
    except ConnectKeyError as e:
        assert e.__cause__ is original


# =============================================================================
# ConnectFailedError Tests
# =============================================================================


def test_connectfailederror_inherits_from_exception():
    """测试 ConnectFailedError 继承自 Exception"""
    assert issubclass(ConnectFailedError, Exception)


def test_connectfailederror_not_keyerror():
    """测试不是 KeyError"""
    assert not issubclass(ConnectFailedError, KeyError)


def test_connectfailederror_can_instantiate():
    """测试可以实例化 ConnectFailedError"""
    error = ConnectFailedError("Test message")
    assert isinstance(error, ConnectFailedError)
    assert isinstance(error, Exception)


def test_connectfailederror_can_raise():
    """测试可以抛出 ConnectFailedError"""
    with pytest.raises(ConnectFailedError):
        raise ConnectFailedError("Operation failed")


def test_connectfailederror_message():
    """测试 ConnectFailedError 消息"""
    msg = "Failed to read file"
    error = ConnectFailedError(msg)
    assert str(error) == msg


def test_connectfailederror_can_catch_as_exception():
    """测试可以作为 Exception 捕获"""
    with pytest.raises(Exception):
        raise ConnectFailedError("Failed")


def test_connectfailederror_empty_message():
    """测试空消息"""
    error = ConnectFailedError()
    assert isinstance(error, ConnectFailedError)


def test_connectfailederror_with_multiple_args():
    """测试多参数实例化"""
    error = ConnectFailedError("Error", "Details")
    assert len(error.args) == 2


def test_connectfailederror_use_case_read():
    """测试读取失败场景"""

    def read_file(path):
        try:
            with open(path, "rb") as f:
                return f.read()
        except Exception as e:
            raise ConnectFailedError(f"Failed to read {path}: {e}") from e

    with pytest.raises(ConnectFailedError) as excinfo:
        read_file("/nonexistent/file.txt")
    assert "Failed to read" in str(excinfo.value)


def test_connectfailederror_use_case_write():
    """测试写入失败场景"""

    def write_file(path, data):
        try:
            with open(path, "wb") as f:
                f.write(data)
        except Exception as e:
            raise ConnectFailedError(f"Failed to write {path}: {e}") from e

    with pytest.raises(ConnectFailedError) as excinfo:
        write_file("/invalid/path/file.txt", b"data")
    assert "Failed to write" in str(excinfo.value)


def test_connectfailederror_with_cause():
    """测试带原因链的异常"""
    original = IOError("File not found")
    try:
        raise ConnectFailedError("Operation failed") from original
    except ConnectFailedError as e:
        assert e.__cause__ is original
        assert isinstance(e.__cause__, IOError)


def test_connectfailederror_nested_exception():
    """测试嵌套异常"""
    try:
        try:
            raise ValueError("Inner error")
        except ValueError as ve:
            raise ConnectFailedError("Outer error") from ve
    except ConnectFailedError as e:
        assert isinstance(e.__cause__, ValueError)


# =============================================================================
# Cross-Error Tests
# =============================================================================


def test_all_errors_are_distinct():
    """测试所有错误类是不同的"""
    assert ConnectRefKeyError is not ConnectKeyError
    assert ConnectRefKeyError is not ConnectFailedError
    assert ConnectKeyError is not ConnectFailedError


def test_error_hierarchy():
    """测试错误层次结构"""
    # Both KeyError subclasses
    assert issubclass(ConnectRefKeyError, KeyError)
    assert issubclass(ConnectKeyError, KeyError)

    # But not subclasses of each other
    assert not issubclass(ConnectRefKeyError, ConnectKeyError)
    assert not issubclass(ConnectKeyError, ConnectRefKeyError)

    # ConnectFailedError is separate
    assert not issubclass(ConnectFailedError, KeyError)
    assert not issubclass(ConnectFailedError, ConnectRefKeyError)
    assert not issubclass(ConnectFailedError, ConnectKeyError)


def test_can_catch_multiple_keyerrors():
    """测试可以捕获多个 KeyError 子类"""

    def raise_ref_key_error():
        raise ConnectRefKeyError("ref error")

    def raise_connect_key_error():
        raise ConnectKeyError("connect error")

    # Can catch both as KeyError
    with pytest.raises(KeyError):
        raise_ref_key_error()

    with pytest.raises(KeyError):
        raise_connect_key_error()


def test_specific_error_catching():
    """测试特定错误捕获"""

    def operation(error_type):
        if error_type == "ref":
            raise ConnectRefKeyError("ref error")
        elif error_type == "key":
            raise ConnectKeyError("key error")
        else:
            raise ConnectFailedError("failed")

    # Catch specific errors
    with pytest.raises(ConnectRefKeyError):
        operation("ref")

    with pytest.raises(ConnectKeyError):
        operation("key")

    with pytest.raises(ConnectFailedError):
        operation("failed")


def test_error_catching_order():
    """测试错误捕获顺序"""

    def risky_operation():
        raise ConnectRefKeyError("error")

    # Specific catches first
    caught_specific = False
    caught_general = False

    try:
        risky_operation()
    except ConnectRefKeyError:
        caught_specific = True
    except KeyError:
        caught_general = True

    assert caught_specific
    assert not caught_general


# =============================================================================
# Error Message Tests
# =============================================================================


def test_error_messages_are_preserved():
    """测试错误消息被保留"""
    messages = [
        "Simple message",
        "Message with 'quotes'",
        "Unicode message: 测试错误",
        "Long " + "x" * 1000 + " message",
    ]

    for msg in messages:
        e1 = ConnectRefKeyError(msg)
        e2 = ConnectKeyError(msg)
        e3 = ConnectFailedError(msg)

        # For KeyError subclasses, message might be wrapped
        assert msg in str(e1) or msg in repr(e1) or msg.replace("'", "\\'") in str(e1)
        assert msg in str(e2) or msg in repr(e2) or msg.replace("'", "\\'") in str(e2)
        assert msg in str(e3)


def test_formatted_error_messages():
    """测试格式化的错误消息"""
    location = "/path/to/file"
    error = ConnectFailedError(f"Failed to access {location}")
    assert location in str(error)


# =============================================================================
# Practical Workflow Tests
# =============================================================================


def test_connect_workflow_errors():
    """测试连接工作流中的错误处理"""

    def connect_operation(location):
        # Validation
        if not location:
            raise ConnectKeyError("location is required")

        # Try operation
        try:
            # Simulate operation
            if location == "invalid":
                raise IOError("Cannot access")
        except IOError as e:
            raise ConnectFailedError(f"Operation failed: {e}") from e

    # Test validation error
    with pytest.raises(ConnectKeyError):
        connect_operation("")

    # Test operation error
    with pytest.raises(ConnectFailedError):
        connect_operation("invalid")


def test_ref_creation_workflow_errors():
    """测试引用创建工作流中的错误处理"""

    def create_ref_from_data(data):
        if "location" not in data:
            raise ConnectRefKeyError("'location' is required")

        location = data["location"]
        if not location:
            raise ConnectKeyError("location cannot be empty")

        # Simulate ref creation succeeds
        return {"location": location, "kind": data.get("kind")}

    # Test missing location
    with pytest.raises(ConnectRefKeyError):
        create_ref_from_data({"kind": "s3"})

    # Test empty location
    with pytest.raises(ConnectKeyError):
        create_ref_from_data({"location": ""})

    # Test success
    result = create_ref_from_data({"location": "test"})
    assert result["location"] == "test"


def test_error_context_preservation():
    """测试错误上下文保留"""

    def inner_operation():
        raise ValueError("Inner error with details")

    def outer_operation():
        try:
            inner_operation()
        except ValueError as e:
            raise ConnectFailedError("Outer operation failed") from e

    with pytest.raises(ConnectFailedError) as excinfo:
        outer_operation()

    error = excinfo.value
    assert error.__cause__ is not None
    assert isinstance(error.__cause__, ValueError)
    assert "Inner error" in str(error.__cause__)


# =============================================================================
# Error Module Tests
# =============================================================================


def test_all_errors_importable():
    """测试所有错误可以导入"""
    from xfintech.connect.common.error import (
        ConnectFailedError,
        ConnectKeyError,
        ConnectRefKeyError,
    )

    assert ConnectRefKeyError is not None
    assert ConnectKeyError is not None
    assert ConnectFailedError is not None


def test_error_module_has_all_classes():
    """测试错误模块包含所有类"""
    import xfintech.connect.common.error as error_module

    assert hasattr(error_module, "ConnectRefKeyError")
    assert hasattr(error_module, "ConnectKeyError")
    assert hasattr(error_module, "ConnectFailedError")


def test_errors_have_docstrings():
    """测试错误类有文档字符串"""
    assert ConnectRefKeyError.__doc__ is not None
    assert ConnectKeyError.__doc__ is not None
    assert ConnectFailedError.__doc__ is not None

    assert len(ConnectRefKeyError.__doc__) > 0
    assert len(ConnectKeyError.__doc__) > 0
    assert len(ConnectFailedError.__doc__) > 0
