"""
Slot Validation Service.
槽位校验规则 runtime 生效服务

提供槽位值的运行时校验能力，支持：
1. 正则表达式校验
2. JSON Schema 校验
3. 类型校验（string/number/boolean/enum/array_enum）
4. 必填校验
"""

import json
import logging
import re
from dataclasses import dataclass, field
from typing import Any

import jsonschema
from jsonschema.exceptions import ValidationError as JsonSchemaValidationError

from app.models.entities import SlotDefinition

logger = logging.getLogger(__name__)


# 错误码定义
class SlotValidationErrorCode:
    """槽位校验错误码"""

    SLOT_REQUIRED_MISSING = "SLOT_REQUIRED_MISSING"
    SLOT_TYPE_INVALID = "SLOT_TYPE_INVALID"
    SLOT_REGEX_MISMATCH = "SLOT_REGEX_MISMATCH"
    SLOT_JSON_SCHEMA_MISMATCH = "SLOT_JSON_SCHEMA_MISMATCH"
    SLOT_VALIDATION_RULE_INVALID = "SLOT_VALIDATION_RULE_INVALID"
    SLOT_ENUM_INVALID = "SLOT_ENUM_INVALID"
    SLOT_ARRAY_ENUM_INVALID = "SLOT_ARRAY_ENUM_INVALID"


@dataclass
class ValidationResult:
    """
    单个槽位校验结果

    Attributes:
        ok: 校验是否通过
        normalized_value: 归一化后的值（如类型转换后）
        error_code: 错误码（校验失败时）
        error_message: 错误描述（校验失败时）
        ask_back_prompt: 追问提示语（校验失败且配置了 ask_back_prompt 时）
    """

    ok: bool
    normalized_value: Any | None = None
    error_code: str | None = None
    error_message: str | None = None
    ask_back_prompt: str | None = None


@dataclass
class SlotValidationError:
    """
    槽位校验错误详情

    Attributes:
        slot_key: 槽位键名
        error_code: 错误码
        error_message: 错误描述
        ask_back_prompt: 追问提示语
    """

    slot_key: str
    error_code: str
    error_message: str
    ask_back_prompt: str | None = None


@dataclass
class BatchValidationResult:
    """
    批量槽位校验结果

    Attributes:
        ok: 是否全部校验通过
        errors: 校验错误列表
        validated_values: 校验通过的值字典
    """

    ok: bool
    errors: list[SlotValidationError] = field(default_factory=list)
    validated_values: dict[str, Any] = field(default_factory=dict)


class SlotValidationService:
    """
    槽位校验服务

    负责在槽位值写回前执行校验，支持：
    - 正则表达式校验
    - JSON Schema 校验
    - 类型校验
    - 必填校验
    """

    # 支持的槽位类型
    VALID_TYPES = ["string", "number", "boolean", "enum", "array_enum"]

    def __init__(self):
        """初始化槽位校验服务"""
        self._schema_cache: dict[str, dict] = {}

    def validate_slot_value(
        self,
        slot_def: dict[str, Any] | SlotDefinition,
        value: Any,
        tenant_id: str | None = None,
    ) -> ValidationResult:
        """
        校验单个槽位值

        校验顺序：
        1. 必填校验（如果 required=true 且值为空）
        2. 类型校验
        3. validation_rule 校验（正则或 JSON Schema）

        Args:
            slot_def: 槽位定义（dict 或 SlotDefinition 对象）
            value: 待校验的值
            tenant_id: 租户 ID（用于日志记录）

        Returns:
            ValidationResult: 校验结果
        """
        # 统一转换为 dict 处理
        if isinstance(slot_def, SlotDefinition):
            slot_dict = {
                "slot_key": slot_def.slot_key,
                "type": slot_def.type,
                "required": slot_def.required,
                "validation_rule": slot_def.validation_rule,
                "ask_back_prompt": slot_def.ask_back_prompt,
            }
        else:
            slot_dict = slot_def

        slot_key = slot_dict.get("slot_key", "unknown")
        slot_type = slot_dict.get("type", "string")
        required = slot_dict.get("required", False)
        validation_rule = slot_dict.get("validation_rule")
        ask_back_prompt = slot_dict.get("ask_back_prompt")

        # 1. 必填校验
        if required and self._is_empty_value(value):
            logger.info(
                f"[SlotValidation] Required slot missing: "
                f"tenant_id={tenant_id}, slot_key={slot_key}"
            )
            return ValidationResult(
                ok=False,
                error_code=SlotValidationErrorCode.SLOT_REQUIRED_MISSING,
                error_message=f"槽位 '{slot_key}' 为必填项",
                ask_back_prompt=ask_back_prompt,
            )

        # 如果值为空且非必填，跳过后续校验
        if self._is_empty_value(value):
            return ValidationResult(ok=True, normalized_value=value)

        # 2. 类型校验
        type_result = self._validate_type(slot_dict, value, tenant_id)
        if not type_result.ok:
            return ValidationResult(
                ok=False,
                error_code=type_result.error_code,
                error_message=type_result.error_message,
                ask_back_prompt=ask_back_prompt,
            )

        normalized_value = type_result.normalized_value

        # 3. validation_rule 校验
        if validation_rule and str(validation_rule).strip():
            rule_result = self._validate_rule(
                slot_dict, normalized_value, tenant_id
            )
            if not rule_result.ok:
                return ValidationResult(
                    ok=False,
                    error_code=rule_result.error_code,
                    error_message=rule_result.error_message,
                    ask_back_prompt=ask_back_prompt,
                )
            normalized_value = rule_result.normalized_value or normalized_value

        logger.debug(
            f"[SlotValidation] Slot validation passed: "
            f"tenant_id={tenant_id}, slot_key={slot_key}, type={slot_type}"
        )

        return ValidationResult(ok=True, normalized_value=normalized_value)

    def validate_slots(
        self,
        slot_defs: list[dict[str, Any] | SlotDefinition],
        values: dict[str, Any],
        tenant_id: str | None = None,
    ) -> BatchValidationResult:
        """
        批量校验多个槽位值

        Args:
            slot_defs: 槽位定义列表
            values: 槽位值字典 {slot_key: value}
            tenant_id: 租户 ID（用于日志记录）

        Returns:
            BatchValidationResult: 批量校验结果
        """
        errors: list[SlotValidationError] = []
        validated_values: dict[str, Any] = {}

        # 构建 slot_def 映射
        slot_def_map: dict[str, dict[str, Any] | SlotDefinition] = {}
        for slot_def in slot_defs:
            if isinstance(slot_def, SlotDefinition):
                slot_def_map[slot_def.slot_key] = slot_def
            else:
                slot_def_map[slot_def.get("slot_key", "")] = slot_def

        # 校验每个提供的值
        for slot_key, value in values.items():
            slot_def = slot_def_map.get(slot_key)
            if not slot_def:
                # 未定义槽位，跳过校验（允许动态槽位）
                validated_values[slot_key] = value
                continue

            result = self.validate_slot_value(slot_def, value, tenant_id)

            if result.ok:
                validated_values[slot_key] = result.normalized_value
            else:
                errors.append(
                    SlotValidationError(
                        slot_key=slot_key,
                        error_code=result.error_code or "UNKNOWN_ERROR",
                        error_message=result.error_message or "校验失败",
                        ask_back_prompt=result.ask_back_prompt,
                    )
                )

        # 检查必填槽位是否缺失
        for slot_def in slot_defs:
            if isinstance(slot_def, SlotDefinition):
                slot_key = slot_def.slot_key
                required = slot_def.required
                ask_back_prompt = slot_def.ask_back_prompt
            else:
                slot_key = slot_def.get("slot_key", "")
                required = slot_def.get("required", False)
                ask_back_prompt = slot_def.get("ask_back_prompt")

            if required and slot_key not in values:
                # 检查是否已经有该错误
                if not any(e.slot_key == slot_key for e in errors):
                    errors.append(
                        SlotValidationError(
                            slot_key=slot_key,
                            error_code=SlotValidationErrorCode.SLOT_REQUIRED_MISSING,
                            error_message=f"槽位 '{slot_key}' 为必填项",
                            ask_back_prompt=ask_back_prompt,
                        )
                    )

        return BatchValidationResult(
            ok=len(errors) == 0,
            errors=errors,
            validated_values=validated_values,
        )

    def _is_empty_value(self, value: Any) -> bool:
        """判断值是否为空"""
        if value is None:
            return True
        if isinstance(value, str) and not value.strip():
            return True
        if isinstance(value, list) and len(value) == 0:
            return True
        return False

    def _validate_type(
        self,
        slot_def: dict[str, Any],
        value: Any,
        tenant_id: str | None,
    ) -> ValidationResult:
        """
        类型校验

        Args:
            slot_def: 槽位定义字典
            value: 待校验的值
            tenant_id: 租户 ID

        Returns:
            ValidationResult: 校验结果
        """
        slot_key = slot_def.get("slot_key", "unknown")
        slot_type = slot_def.get("type", "string")

        if slot_type not in self.VALID_TYPES:
            logger.warning(
                f"[SlotValidation] Unknown slot type: "
                f"tenant_id={tenant_id}, slot_key={slot_key}, type={slot_type}"
            )
            # 未知类型不阻止，只记录警告
            return ValidationResult(ok=True, normalized_value=value)

        try:
            if slot_type == "string":
                if not isinstance(value, str):
                    # 尝试转换为字符串
                    normalized = str(value)
                    return ValidationResult(ok=True, normalized_value=normalized)
                return ValidationResult(ok=True, normalized_value=value)

            elif slot_type == "number":
                if isinstance(value, bool):
                    return ValidationResult(
                        ok=False,
                        error_code=SlotValidationErrorCode.SLOT_TYPE_INVALID,
                        error_message=f"槽位 '{slot_key}' 类型应为数字，但得到布尔值",
                    )
                if isinstance(value, int | float):
                    return ValidationResult(ok=True, normalized_value=value)
                # 尝试转换为数字
                if isinstance(value, str):
                    try:
                        if "." in value:
                            normalized = float(value)
                        else:
                            normalized = int(value)
                        return ValidationResult(ok=True, normalized_value=normalized)
                    except ValueError:
                        pass
                return ValidationResult(
                    ok=False,
                    error_code=SlotValidationErrorCode.SLOT_TYPE_INVALID,
                    error_message=f"槽位 '{slot_key}' 类型应为数字",
                )

            elif slot_type == "boolean":
                if isinstance(value, bool):
                    return ValidationResult(ok=True, normalized_value=value)
                if isinstance(value, str):
                    lower_val = value.lower()
                    if lower_val in ("true", "1", "yes", "是", "真"):
                        return ValidationResult(ok=True, normalized_value=True)
                    if lower_val in ("false", "0", "no", "否", "假"):
                        return ValidationResult(ok=True, normalized_value=False)
                return ValidationResult(
                    ok=False,
                    error_code=SlotValidationErrorCode.SLOT_TYPE_INVALID,
                    error_message=f"槽位 '{slot_key}' 类型应为布尔值",
                )

            elif slot_type == "enum":
                if not isinstance(value, str):
                    return ValidationResult(
                        ok=False,
                        error_code=SlotValidationErrorCode.SLOT_TYPE_INVALID,
                        error_message=f"槽位 '{slot_key}' 类型应为字符串（枚举）",
                    )
                # 如果有选项定义，校验值是否在选项中
                options = slot_def.get("options") or []
                if options and value not in options:
                    return ValidationResult(
                        ok=False,
                        error_code=SlotValidationErrorCode.SLOT_ENUM_INVALID,
                        error_message=f"槽位 '{slot_key}' 的值 '{value}' 不在允许选项 {options} 中",
                    )
                return ValidationResult(ok=True, normalized_value=value)

            elif slot_type == "array_enum":
                if not isinstance(value, list):
                    return ValidationResult(
                        ok=False,
                        error_code=SlotValidationErrorCode.SLOT_TYPE_INVALID,
                        error_message=f"槽位 '{slot_key}' 类型应为数组",
                    )
                # 校验数组元素
                options = slot_def.get("options") or []
                for item in value:
                    if not isinstance(item, str):
                        return ValidationResult(
                            ok=False,
                            error_code=SlotValidationErrorCode.SLOT_ARRAY_ENUM_INVALID,
                            error_message=f"槽位 '{slot_key}' 的数组元素应为字符串",
                        )
                    if options and item not in options:
                        return ValidationResult(
                            ok=False,
                            error_code=SlotValidationErrorCode.SLOT_ARRAY_ENUM_INVALID,
                            error_message=f"槽位 '{slot_key}' 的值 '{item}' 不在允许选项 {options} 中",
                        )
                return ValidationResult(ok=True, normalized_value=value)

        except Exception as e:
            logger.error(
                f"[SlotValidation] Type validation error: "
                f"tenant_id={tenant_id}, slot_key={slot_key}, error={e}"
            )
            return ValidationResult(
                ok=False,
                error_code=SlotValidationErrorCode.SLOT_TYPE_INVALID,
                error_message=f"槽位 '{slot_key}' 类型校验异常: {str(e)}",
            )

        return ValidationResult(ok=True, normalized_value=value)

    def _validate_rule(
        self,
        slot_def: dict[str, Any],
        value: Any,
        tenant_id: str | None,
    ) -> ValidationResult:
        """
        校验规则校验（正则或 JSON Schema）

        判定逻辑：
        1. 如果规则是 JSON 对象字符串（以 { 或 [ 开头），按 JSON Schema 处理
        2. 否则按正则表达式处理

        Args:
            slot_def: 槽位定义字典
            value: 待校验的值
            tenant_id: 租户 ID

        Returns:
            ValidationResult: 校验结果
        """
        slot_key = slot_def.get("slot_key", "unknown")
        validation_rule = str(slot_def.get("validation_rule", "")).strip()

        if not validation_rule:
            return ValidationResult(ok=True, normalized_value=value)

        # 判定是 JSON Schema 还是正则表达式
        # JSON Schema 通常以 { 或 [ 开头
        is_json_schema = validation_rule.strip().startswith(("{", "["))

        if is_json_schema:
            return self._validate_json_schema(
                slot_key, validation_rule, value, tenant_id
            )
        else:
            return self._validate_regex(
                slot_key, validation_rule, value, tenant_id
            )

    def _validate_regex(
        self,
        slot_key: str,
        pattern: str,
        value: Any,
        tenant_id: str | None,
    ) -> ValidationResult:
        """
        正则表达式校验

        Args:
            slot_key: 槽位键名
            pattern: 正则表达式
            value: 待校验的值
            tenant_id: 租户 ID

        Returns:
            ValidationResult: 校验结果
        """
        try:
            # 将值转为字符串进行匹配
            str_value = str(value) if value is not None else ""

            if not re.search(pattern, str_value):
                logger.info(
                    f"[SlotValidation] Regex mismatch: "
                    f"tenant_id={tenant_id}, slot_key={slot_key}"
                )
                return ValidationResult(
                    ok=False,
                    error_code=SlotValidationErrorCode.SLOT_REGEX_MISMATCH,
                    error_message=f"槽位 '{slot_key}' 的值不符合格式要求",
                )

            return ValidationResult(ok=True, normalized_value=value)

        except re.error as e:
            logger.warning(
                f"[SlotValidation] Invalid regex pattern: "
                f"tenant_id={tenant_id}, slot_key={slot_key}, pattern={pattern}, error={e}"
            )
            return ValidationResult(
                ok=False,
                error_code=SlotValidationErrorCode.SLOT_VALIDATION_RULE_INVALID,
                error_message=f"槽位 '{slot_key}' 的校验规则配置无效（非法正则）",
            )

    def _validate_json_schema(
        self,
        slot_key: str,
        schema_str: str,
        value: Any,
        tenant_id: str | None,
    ) -> ValidationResult:
        """
        JSON Schema 校验

        Args:
            slot_key: 槽位键名
            schema_str: JSON Schema 字符串
            value: 待校验的值
            tenant_id: 租户 ID

        Returns:
            ValidationResult: 校验结果
        """
        try:
            # 解析 JSON Schema
            schema = self._schema_cache.get(schema_str)
            if schema is None:
                schema = json.loads(schema_str)
                self._schema_cache[schema_str] = schema

            # 执行校验
            jsonschema.validate(instance=value, schema=schema)
            return ValidationResult(ok=True, normalized_value=value)

        except json.JSONDecodeError as e:
            logger.warning(
                f"[SlotValidation] Invalid JSON schema: "
                f"tenant_id={tenant_id}, slot_key={slot_key}, error={e}"
            )
            return ValidationResult(
                ok=False,
                error_code=SlotValidationErrorCode.SLOT_VALIDATION_RULE_INVALID,
                error_message=f"槽位 '{slot_key}' 的校验规则配置无效（非法 JSON）",
            )

        except JsonSchemaValidationError as e:
            logger.info(
                f"[SlotValidation] JSON schema mismatch: "
                f"tenant_id={tenant_id}, slot_key={slot_key}, error={e.message}"
            )
            return ValidationResult(
                ok=False,
                error_code=SlotValidationErrorCode.SLOT_JSON_SCHEMA_MISMATCH,
                error_message=f"槽位 '{slot_key}' 的值不符合格式要求: {e.message}",
            )

        except Exception as e:
            logger.error(
                f"[SlotValidation] JSON schema validation error: "
                f"tenant_id={tenant_id}, slot_key={slot_key}, error={e}"
            )
            return ValidationResult(
                ok=False,
                error_code=SlotValidationErrorCode.SLOT_VALIDATION_RULE_INVALID,
                error_message=f"槽位 '{slot_key}' 的校验规则执行异常: {str(e)}",
            )

    def clear_cache(self) -> None:
        """清除 JSON Schema 缓存"""
        self._schema_cache.clear()
