"""
Tests for Slot Validation Service.
槽位校验服务单元测试
"""

import pytest

from app.services.mid.slot_validation_service import (
    SlotValidationService,
    SlotValidationErrorCode,
    ValidationResult,
    SlotValidationError,
    BatchValidationResult,
)


class TestSlotValidationService:
    """槽位校验服务测试类"""

    @pytest.fixture
    def service(self):
        """创建校验服务实例"""
        return SlotValidationService()

    @pytest.fixture
    def string_slot_def(self):
        """字符串类型槽位定义"""
        return {
            "slot_key": "name",
            "type": "string",
            "required": False,
            "validation_rule": None,
            "ask_back_prompt": "请输入您的姓名",
        }

    @pytest.fixture
    def required_string_slot_def(self):
        """必填字符串类型槽位定义"""
        return {
            "slot_key": "phone",
            "type": "string",
            "required": True,
            "validation_rule": r"^1[3-9]\d{9}$",
            "ask_back_prompt": "请输入正确的手机号码",
        }

    @pytest.fixture
    def number_slot_def(self):
        """数字类型槽位定义"""
        return {
            "slot_key": "age",
            "type": "number",
            "required": False,
            "validation_rule": None,
            "ask_back_prompt": "请输入年龄",
        }

    @pytest.fixture
    def boolean_slot_def(self):
        """布尔类型槽位定义"""
        return {
            "slot_key": "is_student",
            "type": "boolean",
            "required": False,
            "validation_rule": None,
            "ask_back_prompt": "是否是学生？",
        }

    @pytest.fixture
    def enum_slot_def(self):
        """枚举类型槽位定义"""
        return {
            "slot_key": "grade",
            "type": "enum",
            "required": False,
            "options": ["初一", "初二", "初三", "高一", "高二", "高三"],
            "validation_rule": None,
            "ask_back_prompt": "请选择年级",
        }

    @pytest.fixture
    def array_enum_slot_def(self):
        """数组枚举类型槽位定义"""
        return {
            "slot_key": "subjects",
            "type": "array_enum",
            "required": False,
            "options": ["语文", "数学", "英语", "物理", "化学"],
            "validation_rule": None,
            "ask_back_prompt": "请选择学科",
        }

    @pytest.fixture
    def json_schema_slot_def(self):
        """JSON Schema 校验槽位定义"""
        return {
            "slot_key": "email",
            "type": "string",
            "required": True,
            "validation_rule": '{"type": "string", "format": "email"}',
            "ask_back_prompt": "请输入有效的邮箱地址",
        }

    class TestBasicValidation:
        """基础校验测试"""

        def test_empty_validation_rule(self, service, string_slot_def):
            """测试空校验规则（应通过）"""
            string_slot_def["validation_rule"] = None
            result = service.validate_slot_value(string_slot_def, "test")
            assert result.ok is True
            assert result.normalized_value == "test"

        def test_whitespace_validation_rule(self, service, string_slot_def):
            """测试空白校验规则（应通过）"""
            string_slot_def["validation_rule"] = "   "
            result = service.validate_slot_value(string_slot_def, "test")
            assert result.ok is True

        def test_no_slot_definition(self, service):
            """测试无槽位定义（动态槽位）"""
            # 使用最小定义
            minimal_def = {"slot_key": "dynamic_field"}
            result = service.validate_slot_value(minimal_def, "any_value")
            assert result.ok is True

    class TestRegexValidation:
        """正则表达式校验测试"""

        def test_regex_match(self, service, required_string_slot_def):
            """测试正则匹配成功"""
            result = service.validate_slot_value(
                required_string_slot_def, "13800138000"
            )
            assert result.ok is True
            assert result.normalized_value == "13800138000"

        def test_regex_mismatch(self, service, required_string_slot_def):
            """测试正则匹配失败"""
            result = service.validate_slot_value(
                required_string_slot_def, "invalid_phone"
            )
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_REGEX_MISMATCH
            assert result.ask_back_prompt == "请输入正确的手机号码"

        def test_regex_invalid_pattern(self, service, string_slot_def):
            """测试非法正则表达式"""
            string_slot_def["validation_rule"] = "[invalid("
            result = service.validate_slot_value(string_slot_def, "test")
            assert result.ok is False
            assert (
                result.error_code
                == SlotValidationErrorCode.SLOT_VALIDATION_RULE_INVALID
            )

        def test_regex_with_chinese(self, service, string_slot_def):
            """测试包含中文的正则"""
            string_slot_def["validation_rule"] = r"^[\u4e00-\u9fa5]{2,4}$"
            result = service.validate_slot_value(string_slot_def, "张三")
            assert result.ok is True

            result = service.validate_slot_value(string_slot_def, "John")
            assert result.ok is False

    class TestJsonSchemaValidation:
        """JSON Schema 校验测试"""

        def test_json_schema_match(self, service):
            """测试 JSON Schema 匹配成功"""
            slot_def = {
                "slot_key": "config",
                "type": "object",
                "validation_rule": '{"type": "object", "properties": {"name": {"type": "string"}}}',
            }
            result = service.validate_slot_value(slot_def, {"name": "test"})
            assert result.ok is True

        def test_json_schema_mismatch(self, service):
            """测试 JSON Schema 匹配失败"""
            slot_def = {
                "slot_key": "count",
                "type": "number",
                "validation_rule": '{"type": "integer", "minimum": 0, "maximum": 100}',
                "ask_back_prompt": "请输入0-100之间的整数",
            }
            result = service.validate_slot_value(slot_def, 150)
            assert result.ok is False
            assert (
                result.error_code == SlotValidationErrorCode.SLOT_JSON_SCHEMA_MISMATCH
            )
            assert result.ask_back_prompt == "请输入0-100之间的整数"

        def test_json_schema_invalid_json(self, service, string_slot_def):
            """测试非法 JSON Schema"""
            string_slot_def["validation_rule"] = "{invalid json}"
            result = service.validate_slot_value(string_slot_def, "test")
            assert result.ok is False
            assert (
                result.error_code
                == SlotValidationErrorCode.SLOT_VALIDATION_RULE_INVALID
            )

        def test_json_schema_array(self, service):
            """测试数组类型的 JSON Schema"""
            slot_def = {
                "slot_key": "items",
                "type": "array",
                "validation_rule": '{"type": "array", "items": {"type": "string"}}',
            }
            result = service.validate_slot_value(slot_def, ["a", "b", "c"])
            assert result.ok is True

            result = service.validate_slot_value(slot_def, [1, 2, 3])
            assert result.ok is False

    class TestRequiredValidation:
        """必填校验测试"""

        def test_required_missing_none(self, service, required_string_slot_def):
            """测试必填字段为 None"""
            result = service.validate_slot_value(
                required_string_slot_def, None
            )
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_REQUIRED_MISSING

        def test_required_missing_empty_string(self, service, required_string_slot_def):
            """测试必填字段为空字符串"""
            result = service.validate_slot_value(required_string_slot_def, "")
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_REQUIRED_MISSING

        def test_required_missing_whitespace(self, service, required_string_slot_def):
            """测试必填字段为空白字符"""
            result = service.validate_slot_value(required_string_slot_def, "   ")
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_REQUIRED_MISSING

        def test_required_present(self, service, required_string_slot_def):
            """测试必填字段有值"""
            result = service.validate_slot_value(
                required_string_slot_def, "13800138000"
            )
            assert result.ok is True

        def test_not_required_empty(self, service, string_slot_def):
            """测试非必填字段为空"""
            result = service.validate_slot_value(string_slot_def, "")
            assert result.ok is True

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

        def test_string_type(self, service, string_slot_def):
            """测试字符串类型"""
            result = service.validate_slot_value(string_slot_def, "hello")
            assert result.ok is True
            assert result.normalized_value == "hello"

        def test_string_type_conversion(self, service, string_slot_def):
            """测试字符串类型自动转换"""
            result = service.validate_slot_value(string_slot_def, 123)
            assert result.ok is True
            assert result.normalized_value == "123"

        def test_number_type_integer(self, service, number_slot_def):
            """测试数字类型 - 整数"""
            result = service.validate_slot_value(number_slot_def, 25)
            assert result.ok is True
            assert result.normalized_value == 25

        def test_number_type_float(self, service, number_slot_def):
            """测试数字类型 - 浮点数"""
            result = service.validate_slot_value(number_slot_def, 25.5)
            assert result.ok is True
            assert result.normalized_value == 25.5

        def test_number_type_string_conversion(self, service, number_slot_def):
            """测试数字类型 - 字符串转换"""
            result = service.validate_slot_value(number_slot_def, "30")
            assert result.ok is True
            assert result.normalized_value == 30

        def test_number_type_invalid(self, service, number_slot_def):
            """测试数字类型 - 无效值"""
            result = service.validate_slot_value(number_slot_def, "not_a_number")
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_TYPE_INVALID

        def test_number_type_reject_boolean(self, service, number_slot_def):
            """测试数字类型 - 拒绝布尔值"""
            result = service.validate_slot_value(number_slot_def, True)
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_TYPE_INVALID

        def test_boolean_type_true(self, service, boolean_slot_def):
            """测试布尔类型 - True"""
            result = service.validate_slot_value(boolean_slot_def, True)
            assert result.ok is True
            assert result.normalized_value is True

        def test_boolean_type_false(self, service, boolean_slot_def):
            """测试布尔类型 - False"""
            result = service.validate_slot_value(boolean_slot_def, False)
            assert result.ok is True
            assert result.normalized_value is False

        def test_boolean_type_string_true(self, service, boolean_slot_def):
            """测试布尔类型 - 字符串 true"""
            result = service.validate_slot_value(boolean_slot_def, "true")
            assert result.ok is True
            assert result.normalized_value is True

        def test_boolean_type_string_yes(self, service, boolean_slot_def):
            """测试布尔类型 - 字符串 yes/是"""
            result = service.validate_slot_value(boolean_slot_def, "是")
            assert result.ok is True
            assert result.normalized_value is True

        def test_boolean_type_string_false(self, service, boolean_slot_def):
            """测试布尔类型 - 字符串 false"""
            result = service.validate_slot_value(boolean_slot_def, "false")
            assert result.ok is True
            assert result.normalized_value is False

        def test_boolean_type_invalid(self, service, boolean_slot_def):
            """测试布尔类型 - 无效值"""
            result = service.validate_slot_value(boolean_slot_def, "maybe")
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_TYPE_INVALID

        def test_enum_type_valid(self, service, enum_slot_def):
            """测试枚举类型 - 有效值"""
            result = service.validate_slot_value(enum_slot_def, "高一")
            assert result.ok is True
            assert result.normalized_value == "高一"

        def test_enum_type_invalid(self, service, enum_slot_def):
            """测试枚举类型 - 无效值"""
            result = service.validate_slot_value(enum_slot_def, "大一")
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_ENUM_INVALID

        def test_enum_type_not_string(self, service, enum_slot_def):
            """测试枚举类型 - 非字符串"""
            result = service.validate_slot_value(enum_slot_def, 123)
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_TYPE_INVALID

        def test_array_enum_type_valid(self, service, array_enum_slot_def):
            """测试数组枚举类型 - 有效值"""
            result = service.validate_slot_value(
                array_enum_slot_def, ["语文", "数学"]
            )
            assert result.ok is True

        def test_array_enum_type_invalid_item(self, service, array_enum_slot_def):
            """测试数组枚举类型 - 无效元素"""
            result = service.validate_slot_value(
                array_enum_slot_def, ["语文", "生物"]
            )
            assert result.ok is False
            assert (
                result.error_code == SlotValidationErrorCode.SLOT_ARRAY_ENUM_INVALID
            )

        def test_array_enum_type_not_array(self, service, array_enum_slot_def):
            """测试数组枚举类型 - 非数组"""
            result = service.validate_slot_value(array_enum_slot_def, "语文")
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_TYPE_INVALID

        def test_array_enum_type_non_string_item(self, service, array_enum_slot_def):
            """测试数组枚举类型 - 非字符串元素"""
            result = service.validate_slot_value(array_enum_slot_def, ["语文", 123])
            assert result.ok is False
            assert (
                result.error_code == SlotValidationErrorCode.SLOT_ARRAY_ENUM_INVALID
            )

    class TestBatchValidation:
        """批量校验测试"""

        def test_batch_all_valid(self, service, string_slot_def, number_slot_def):
            """测试批量校验 - 全部通过"""
            slot_defs = [string_slot_def, number_slot_def]
            values = {"name": "张三", "age": 25}
            result = service.validate_slots(slot_defs, values)
            assert result.ok is True
            assert len(result.errors) == 0
            assert result.validated_values["name"] == "张三"
            assert result.validated_values["age"] == 25

        def test_batch_some_invalid(self, service, string_slot_def, number_slot_def):
            """测试批量校验 - 部分失败"""
            slot_defs = [string_slot_def, number_slot_def]
            values = {"name": "张三", "age": "not_a_number"}
            result = service.validate_slots(slot_defs, values)
            assert result.ok is False
            assert len(result.errors) == 1
            assert result.errors[0].slot_key == "age"

        def test_batch_missing_required(
            self, service, required_string_slot_def, string_slot_def
        ):
            """测试批量校验 - 缺失必填字段"""
            slot_defs = [required_string_slot_def, string_slot_def]
            values = {"name": "张三"}  # 缺少 phone
            result = service.validate_slots(slot_defs, values)
            assert result.ok is False
            assert len(result.errors) == 1
            assert result.errors[0].slot_key == "phone"
            assert (
                result.errors[0].error_code
                == SlotValidationErrorCode.SLOT_REQUIRED_MISSING
            )

        def test_batch_undefined_slot(self, service, string_slot_def):
            """测试批量校验 - 未定义槽位"""
            slot_defs = [string_slot_def]
            values = {"name": "张三", "undefined_field": "value"}
            result = service.validate_slots(slot_defs, values)
            assert result.ok is True
            # 未定义槽位应允许通过
            assert "undefined_field" in result.validated_values

    class TestCombinedValidation:
        """组合校验测试（类型 + 正则/JSON Schema）"""

        def test_type_and_regex_both_pass(self, service):
            """测试类型和正则都通过"""
            slot_def = {
                "slot_key": "code",
                "type": "string",
                "required": True,
                "validation_rule": r"^[A-Z]{2}\d{4}$",
            }
            result = service.validate_slot_value(slot_def, "AB1234")
            assert result.ok is True

        def test_type_pass_regex_fail(self, service):
            """测试类型通过但正则失败"""
            slot_def = {
                "slot_key": "code",
                "type": "string",
                "required": True,
                "validation_rule": r"^[A-Z]{2}\d{4}$",
            }
            result = service.validate_slot_value(slot_def, "ab1234")
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_REGEX_MISMATCH

        def test_type_fail_no_regex_check(self, service):
            """测试类型失败时不执行正则校验"""
            slot_def = {
                "slot_key": "code",
                "type": "number",
                "required": True,
                "validation_rule": r"^\d+$",
            }
            result = service.validate_slot_value(slot_def, "not_a_number")
            assert result.ok is False
            assert result.error_code == SlotValidationErrorCode.SLOT_TYPE_INVALID

    class TestAskBackPrompt:
        """追问提示语测试"""

        def test_ask_back_prompt_on_validation_fail(self, service):
            """测试校验失败时返回 ask_back_prompt"""
            slot_def = {
                "slot_key": "email",
                "type": "string",
                "required": True,
                "validation_rule": r"^[\w\.-]+@[\w\.-]+\.\w+$",
                "ask_back_prompt": "请输入有效的邮箱地址，如 example@domain.com",
            }
            result = service.validate_slot_value(slot_def, "invalid_email")
            assert result.ok is False
            assert result.ask_back_prompt == "请输入有效的邮箱地址，如 example@domain.com"

        def test_no_ask_back_prompt_on_success(self, service, string_slot_def):
            """测试校验通过时不返回 ask_back_prompt"""
            result = service.validate_slot_value(string_slot_def, "valid")
            assert result.ok is True
            assert result.ask_back_prompt is None

        def test_ask_back_prompt_on_required_missing(self, service):
            """测试必填缺失时返回 ask_back_prompt"""
            slot_def = {
                "slot_key": "name",
                "type": "string",
                "required": True,
                "ask_back_prompt": "请告诉我们您的姓名",
            }
            result = service.validate_slot_value(slot_def, "")
            assert result.ok is False
            assert result.ask_back_prompt == "请告诉我们您的姓名"


class TestSlotValidationErrorCode:
    """错误码测试"""

    def test_error_code_values(self):
        """测试错误码值"""
        assert SlotValidationErrorCode.SLOT_REQUIRED_MISSING == "SLOT_REQUIRED_MISSING"
        assert SlotValidationErrorCode.SLOT_TYPE_INVALID == "SLOT_TYPE_INVALID"
        assert SlotValidationErrorCode.SLOT_REGEX_MISMATCH == "SLOT_REGEX_MISMATCH"
        assert (
            SlotValidationErrorCode.SLOT_JSON_SCHEMA_MISMATCH
            == "SLOT_JSON_SCHEMA_MISMATCH"
        )
        assert (
            SlotValidationErrorCode.SLOT_VALIDATION_RULE_INVALID
            == "SLOT_VALIDATION_RULE_INVALID"
        )


class TestValidationResult:
    """ValidationResult 测试"""

    def test_success_result(self):
        """测试成功结果"""
        result = ValidationResult(ok=True, normalized_value="test")
        assert result.ok is True
        assert result.normalized_value == "test"
        assert result.error_code is None
        assert result.error_message is None

    def test_failure_result(self):
        """测试失败结果"""
        result = ValidationResult(
            ok=False,
            error_code="SLOT_REGEX_MISMATCH",
            error_message="格式不正确",
            ask_back_prompt="请重新输入",
        )
        assert result.ok is False
        assert result.error_code == "SLOT_REGEX_MISMATCH"
        assert result.error_message == "格式不正确"
        assert result.ask_back_prompt == "请重新输入"
