"""
验证工具模块的单元测试

测试所有验证函数的正常情况、边界条件和异常情况。
"""

import pytest
from pathlib import Path

from src.symphra_excel.utils.validators import (
    # 单元格和范围验证
    validate_cell_reference,
    validate_range_reference,
    # 文件路径验证
    validate_file_path,
    validate_excel_file_path,
    # 颜色值验证
    validate_color_hex,
    validate_color_rgb,
    # 数值范围验证
    validate_number_range,
    validate_positive_number,
    # 字符串验证
    validate_non_empty_string,
    validate_string_length,
    validate_string_pattern,
    # 集合和类型验证
    validate_in_collection,
    validate_type,
    validate_callable,
    # 复合验证
    validate_percentage,
    validate_opacity,
    validate_dimension,
)
from src.symphra_excel.utils.exceptions import ValidationError


# ==================== 单元格和范围验证测试 ====================


class TestCellReferenceValidation:
    """测试单元格引用验证"""

    def test_valid_single_column_cell(self):
        """测试单列单元格引用"""
        col, row = validate_cell_reference("A1")
        assert col == "A"
        assert row == 1

    def test_valid_multi_column_cell(self):
        """测试多列单元格引用"""
        col, row = validate_cell_reference("AB123")
        assert col == "AB"
        assert row == 123

    def test_lowercase_conversion(self):
        """测试小写字母自动转大写"""
        col, row = validate_cell_reference("ab123")
        assert col == "AB"
        assert row == 123

    def test_with_whitespace(self):
        """测试带空格的引用"""
        col, row = validate_cell_reference("  A1  ")
        assert col == "A"
        assert row == 1

    def test_max_row(self):
        """测试最大行号"""
        col, row = validate_cell_reference("A1048576")
        assert row == 1048576

    def test_invalid_format(self):
        """测试无效格式"""
        with pytest.raises(ValidationError, match="无效的单元格引用"):
            validate_cell_reference("123A")

        with pytest.raises(ValidationError, match="无效的单元格引用"):
            validate_cell_reference("ABC")

        with pytest.raises(ValidationError, match="无效的单元格引用"):
            validate_cell_reference("123")

    def test_empty_string(self):
        """测试空字符串"""
        with pytest.raises(ValidationError, match="必须是非空字符串"):
            validate_cell_reference("")

    def test_none_value(self):
        """测试None值"""
        with pytest.raises(ValidationError, match="必须是非空字符串"):
            validate_cell_reference(None)

    def test_row_out_of_range(self):
        """测试行号超出范围"""
        with pytest.raises(ValidationError, match="行号必须在 1-1048576 之间"):
            validate_cell_reference("A1048577")

        with pytest.raises(ValidationError, match="行号必须在 1-1048576 之间"):
            validate_cell_reference("A0")


class TestRangeReferenceValidation:
    """测试范围引用验证"""

    def test_valid_range(self):
        """测试有效范围"""
        start, end = validate_range_reference("A1:B10")
        assert start == "A1"
        assert end == "B10"

    def test_single_cell_range(self):
        """测试单单元格范围"""
        start, end = validate_range_reference("A1")
        assert start == "A1"
        assert end == "A1"

    def test_with_sheet_name(self):
        """测试带工作表名的范围"""
        start, end = validate_range_reference("Sheet1!A1:B10")
        assert start == "A1"
        assert end == "B10"

    def test_invalid_range(self):
        """测试无效范围"""
        with pytest.raises(ValidationError):
            validate_range_reference("A1:B10:C20")

    def test_empty_string(self):
        """测试空字符串"""
        with pytest.raises(ValidationError, match="必须是非空字符串"):
            validate_range_reference("")


# ==================== 文件路径验证测试 ====================


class TestFilePathValidation:
    """测试文件路径验证"""

    def test_string_path(self):
        """测试字符串路径"""
        result = validate_file_path("/tmp/test.xlsx")
        assert isinstance(result, Path)
        assert str(result) == "/tmp/test.xlsx"

    def test_path_object(self):
        """测试Path对象"""
        path = Path("/tmp/test.xlsx")
        result = validate_file_path(path)
        assert result == path

    def test_empty_string(self):
        """测试空字符串"""
        with pytest.raises(ValidationError, match="不能为空字符串"):
            validate_file_path("")

    def test_none_value(self):
        """测试None值"""
        with pytest.raises(ValidationError, match="不能为 None"):
            validate_file_path(None)

    def test_invalid_type(self):
        """测试无效类型"""
        with pytest.raises(ValidationError, match="必须是 str 或 Path 类型"):
            validate_file_path(123)

    def test_extension_validation(self):
        """测试扩展名验证"""
        # 有效扩展名
        result = validate_file_path("/tmp/test.xlsx", extensions=[".xlsx", ".xls"])
        assert result.suffix == ".xlsx"

        # 无效扩展名
        with pytest.raises(ValidationError, match="文件扩展名必须是"):
            validate_file_path("/tmp/test.txt", extensions=[".xlsx", ".xls"])

    def test_extension_normalization(self):
        """测试扩展名标准化"""
        # 大小写不敏感
        result = validate_file_path("/tmp/test.XLSX", extensions=[".xlsx"])
        assert result.suffix == ".XLSX"

        # 不带点的扩展名
        result = validate_file_path("/tmp/test.xlsx", extensions=["xlsx"])
        assert result.suffix == ".xlsx"


class TestExcelFilePathValidation:
    """测试Excel文件路径验证"""

    def test_valid_xlsx(self):
        """测试有效的xlsx文件"""
        result = validate_excel_file_path("/tmp/test.xlsx")
        assert result.suffix == ".xlsx"

    def test_valid_xlsm(self):
        """测试有效的xlsm文件"""
        result = validate_excel_file_path("/tmp/test.xlsm")
        assert result.suffix == ".xlsm"

    def test_invalid_extension(self):
        """测试无效扩展名"""
        with pytest.raises(ValidationError, match="文件扩展名必须是"):
            validate_excel_file_path("/tmp/test.txt")


# ==================== 颜色值验证测试 ====================


class TestColorHexValidation:
    """测试十六进制颜色验证"""

    def test_valid_6_digit_with_hash(self):
        """测试6位带#的颜色"""
        result = validate_color_hex("#FFFFFF")
        assert result == "#FFFFFFFF"

    def test_valid_6_digit_without_hash(self):
        """测试6位不带#的颜色"""
        result = validate_color_hex("FF0000")
        assert result == "#FFFF0000"

    def test_valid_3_digit(self):
        """测试3位颜色"""
        result = validate_color_hex("#FFF")
        assert result == "#FFFFFFFF"

        result = validate_color_hex("F00")
        assert result == "#FFFF0000"

    def test_valid_8_digit(self):
        """测试8位ARGB颜色"""
        result = validate_color_hex("#80FFFFFF")
        assert result == "#80FFFFFF"

    def test_lowercase_conversion(self):
        """测试小写转大写"""
        result = validate_color_hex("#ffffff")
        assert result == "#FFFFFFFF"

    def test_invalid_format(self):
        """测试无效格式"""
        with pytest.raises(ValidationError, match="无效的十六进制颜色值"):
            validate_color_hex("GGGGGG")

        with pytest.raises(ValidationError, match="无效的十六进制颜色值"):
            validate_color_hex("#FF")

        with pytest.raises(ValidationError, match="无效的十六进制颜色值"):
            validate_color_hex("#FFFFFFFFF")

    def test_empty_string(self):
        """测试空字符串"""
        with pytest.raises(ValidationError, match="必须是非空字符串"):
            validate_color_hex("")


class TestColorRGBValidation:
    """测试RGB颜色验证"""

    def test_valid_rgb(self):
        """测试有效RGB值"""
        r, g, b, a = validate_color_rgb(255, 0, 0)
        assert (r, g, b, a) == (255, 0, 0, 255)

    def test_valid_rgba(self):
        """测试有效RGBA值"""
        r, g, b, a = validate_color_rgb(255, 0, 0, 128)
        assert (r, g, b, a) == (255, 0, 0, 128)

    def test_boundary_values(self):
        """测试边界值"""
        r, g, b, a = validate_color_rgb(0, 0, 0, 0)
        assert (r, g, b, a) == (0, 0, 0, 0)

        r, g, b, a = validate_color_rgb(255, 255, 255, 255)
        assert (r, g, b, a) == (255, 255, 255, 255)

    def test_out_of_range(self):
        """测试超出范围"""
        with pytest.raises(ValidationError, match="分量必须在 0-255 之间"):
            validate_color_rgb(256, 0, 0)

        with pytest.raises(ValidationError, match="分量必须在 0-255 之间"):
            validate_color_rgb(0, -1, 0)

    def test_invalid_type(self):
        """测试无效类型"""
        with pytest.raises(ValidationError, match="分量必须是整数"):
            validate_color_rgb(255.5, 0, 0)


# ==================== 数值范围验证测试 ====================


class TestNumberRangeValidation:
    """测试数值范围验证"""

    def test_valid_in_range(self):
        """测试范围内的值"""
        result = validate_number_range(5, min_value=0, max_value=10)
        assert result == 5

    def test_boundary_values_inclusive(self):
        """测试闭区间边界值"""
        result = validate_number_range(0, min_value=0, max_value=10, inclusive=True)
        assert result == 0

        result = validate_number_range(10, min_value=0, max_value=10, inclusive=True)
        assert result == 10

    def test_boundary_values_exclusive(self):
        """测试开区间边界值"""
        with pytest.raises(ValidationError):
            validate_number_range(0, min_value=0, max_value=10, inclusive=False)

        with pytest.raises(ValidationError):
            validate_number_range(10, min_value=0, max_value=10, inclusive=False)

    def test_only_min_value(self):
        """测试仅最小值"""
        result = validate_number_range(100, min_value=0)
        assert result == 100

    def test_only_max_value(self):
        """测试仅最大值"""
        result = validate_number_range(-100, max_value=0)
        assert result == -100

    def test_float_values(self):
        """测试浮点数"""
        result = validate_number_range(5.5, min_value=0.0, max_value=10.0)
        assert result == 5.5

    def test_out_of_range(self):
        """测试超出范围"""
        with pytest.raises(ValidationError, match="必须 >="):
            validate_number_range(-1, min_value=0, max_value=10)

        with pytest.raises(ValidationError, match="必须 <="):
            validate_number_range(11, min_value=0, max_value=10)

    def test_custom_value_name(self):
        """测试自定义值名称"""
        with pytest.raises(ValidationError, match="年龄必须"):
            validate_number_range(-1, min_value=0, value_name="年龄")


class TestPositiveNumberValidation:
    """测试正数验证"""

    def test_positive_number(self):
        """测试正数"""
        result = validate_positive_number(5)
        assert result == 5

    def test_zero_not_allowed(self):
        """测试不允许0"""
        with pytest.raises(ValidationError, match="必须 > 0"):
            validate_positive_number(0)

    def test_zero_allowed(self):
        """测试允许0"""
        result = validate_positive_number(0, allow_zero=True)
        assert result == 0

    def test_negative_number(self):
        """测试负数"""
        with pytest.raises(ValidationError, match="必须 >"):
            validate_positive_number(-5)

    def test_float_positive(self):
        """测试正浮点数"""
        result = validate_positive_number(5.5)
        assert result == 5.5


# ==================== 字符串验证测试 ====================


class TestNonEmptyStringValidation:
    """测试非空字符串验证"""

    def test_valid_string(self):
        """测试有效字符串"""
        result = validate_non_empty_string("hello")
        assert result == "hello"

    def test_string_with_whitespace(self):
        """测试带空格的字符串"""
        result = validate_non_empty_string("  hello  ", strip=True)
        assert result == "hello"

    def test_whitespace_only(self):
        """测试仅空格"""
        with pytest.raises(ValidationError, match="不能为空"):
            validate_non_empty_string("   ", strip=True)

    def test_empty_string(self):
        """测试空字符串"""
        with pytest.raises(ValidationError, match="不能为空"):
            validate_non_empty_string("")

    def test_none_value(self):
        """测试None值"""
        with pytest.raises(ValidationError, match="不能为 None"):
            validate_non_empty_string(None)

    def test_no_strip(self):
        """测试不去除空格"""
        result = validate_non_empty_string("  hello  ", strip=False)
        assert result == "  hello  "


class TestStringLengthValidation:
    """测试字符串长度验证"""

    def test_valid_length(self):
        """测试有效长度"""
        result = validate_string_length("hello", min_length=1, max_length=10)
        assert result == "hello"

    def test_min_length(self):
        """测试最小长度"""
        with pytest.raises(ValidationError, match="长度必须 >="):
            validate_string_length("hi", min_length=5)

    def test_max_length(self):
        """测试最大长度"""
        with pytest.raises(ValidationError, match="长度必须 <="):
            validate_string_length("hello world", max_length=5)

    def test_exact_length(self):
        """测试精确长度"""
        result = validate_string_length("hello", min_length=5, max_length=5)
        assert result == "hello"


class TestStringPatternValidation:
    """测试字符串模式验证"""

    def test_valid_pattern(self):
        """测试有效模式"""
        result = validate_string_pattern("abc123", r"^[a-z]+\d+$")
        assert result == "abc123"

    def test_invalid_pattern(self):
        """测试无效模式"""
        with pytest.raises(ValidationError, match="格式无效"):
            validate_string_pattern("123abc", r"^[a-z]+\d+$")

    def test_email_pattern(self):
        """测试邮箱模式"""
        pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
        result = validate_string_pattern("test@example.com", pattern)
        assert result == "test@example.com"


# ==================== 集合和类型验证测试 ====================


class TestInCollectionValidation:
    """测试集合成员验证"""

    def test_in_list(self):
        """测试在列表中"""
        result = validate_in_collection("apple", ["apple", "banana", "orange"])
        assert result == "apple"

    def test_in_set(self):
        """测试在集合中"""
        result = validate_in_collection(1, {1, 2, 3})
        assert result == 1

    def test_not_in_collection(self):
        """测试不在集合中"""
        with pytest.raises(ValidationError, match="必须是以下之一"):
            validate_in_collection("grape", ["apple", "banana", "orange"])


class TestTypeValidation:
    """测试类型验证"""

    def test_valid_type(self):
        """测试有效类型"""
        result = validate_type(123, int)
        assert result == 123

    def test_multiple_types(self):
        """测试多个类型"""
        result = validate_type("hello", (str, int))
        assert result == "hello"

        result = validate_type(123, (str, int))
        assert result == 123

    def test_invalid_type(self):
        """测试无效类型"""
        with pytest.raises(ValidationError, match="必须是 int 类型"):
            validate_type("hello", int)

    def test_none_not_allowed(self):
        """测试不允许None"""
        with pytest.raises(ValidationError):
            validate_type(None, int)

    def test_none_allowed(self):
        """测试允许None"""
        result = validate_type(None, int, allow_none=True)
        assert result is None


class TestCallableValidation:
    """测试可调用验证"""

    def test_function(self):
        """测试函数"""

        def my_func():
            pass

        result = validate_callable(my_func)
        assert result is my_func

    def test_lambda(self):
        """测试lambda"""
        def func(x):
            return x * 2
        result = validate_callable(func)
        assert result is func

    def test_class(self):
        """测试类"""

        class MyClass:
            pass

        result = validate_callable(MyClass)
        assert result is MyClass

    def test_not_callable(self):
        """测试不可调用"""
        with pytest.raises(ValidationError, match="必须是可调用对象"):
            validate_callable(123)


# ==================== 复合验证测试 ====================


class TestPercentageValidation:
    """测试百分比验证"""

    def test_valid_percentage(self):
        """测试有效百分比"""
        result = validate_percentage(50)
        assert result == 50.0
        assert isinstance(result, float)

    def test_boundary_values(self):
        """测试边界值"""
        assert validate_percentage(0) == 0.0
        assert validate_percentage(100) == 100.0

    def test_out_of_range(self):
        """测试超出范围"""
        with pytest.raises(ValidationError):
            validate_percentage(-1)

        with pytest.raises(ValidationError):
            validate_percentage(101)


class TestOpacityValidation:
    """测试不透明度验证"""

    def test_valid_opacity(self):
        """测试有效不透明度"""
        result = validate_opacity(0.5)
        assert result == 0.5

    def test_boundary_values(self):
        """测试边界值"""
        assert validate_opacity(0.0) == 0.0
        assert validate_opacity(1.0) == 1.0

    def test_out_of_range(self):
        """测试超出范围"""
        with pytest.raises(ValidationError):
            validate_opacity(-0.1)

        with pytest.raises(ValidationError):
            validate_opacity(1.1)


class TestDimensionValidation:
    """测试尺寸验证"""

    def test_valid_dimensions(self):
        """测试有效尺寸"""
        width, height = validate_dimension(width=100, height=200)
        assert width == 100
        assert height == 200

    def test_none_dimensions(self):
        """测试None尺寸"""
        width, height = validate_dimension()
        assert width is None
        assert height is None

    def test_partial_dimensions(self):
        """测试部分尺寸"""
        width, height = validate_dimension(width=100)
        assert width == 100
        assert height is None

    def test_out_of_range(self):
        """测试超出范围"""
        with pytest.raises(ValidationError, match="宽度"):
            validate_dimension(width=0)

        with pytest.raises(ValidationError, match="高度"):
            validate_dimension(height=20000)

    def test_custom_limits(self):
        """测试自定义限制"""
        width, height = validate_dimension(
            width=50,
            height=50,
            min_width=10,
            max_width=100,
            min_height=10,
            max_height=100,
        )
        assert width == 50
        assert height == 50


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


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

    def test_unicode_strings(self):
        """测试Unicode字符串"""
        result = validate_non_empty_string("你好世界")
        assert result == "你好世界"

    def test_very_long_string(self):
        """测试超长字符串"""
        long_string = "a" * 10000
        result = validate_non_empty_string(long_string)
        assert len(result) == 10000

    def test_special_characters_in_path(self):
        """测试路径中的特殊字符"""
        result = validate_file_path("/tmp/test file (1).xlsx")
        assert "test file (1)" in str(result)

    def test_very_large_numbers(self):
        """测试很大的数字"""
        result = validate_number_range(10**10, min_value=0)
        assert result == 10**10

    def test_very_small_numbers(self):
        """测试很小的数字"""
        result = validate_number_range(0.0000001, min_value=0.0)
        assert result == 0.0000001


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