"""
业务工具模块 - API参数处理和JSON Schema操作

这个模块提供了API参数到JSON Schema转换、参数验证、API配置操作等核心业务功能。
它是连接业务API配置和MCP工具定义的桥梁。

主要功能:
1. API参数列表转换为JSON Schema
2. 参数验证和默认值处理
3. Schema提示文本生成
4. API配置数组操作
5. 必填参数检查

核心组件:
- 参数类型常量定义
- JSON Schema生成器
- 参数验证器
- 配置操作工具

设计原则:
- 类型安全：严格的类型检查和转换
- 容错性：完善的异常处理机制
- 可扩展性：支持新的参数类型和验证规则
- 标准化：符合JSON Schema规范

作者: lzwcai
版本: 1.0.0
"""

import json
import copy
from typing import Dict, List, Any, Optional, Union
from ..util.logger_config import get_logger

# 获取日志器实例
logger = get_logger(__name__)


# ==================== 常量定义 ====================

class ParamType:
    """
    API参数类型常量

    定义业务平台API参数的所有支持类型。
    这些类型会被映射到对应的JSON Schema类型。
    """
    STRING = "STRING"      # 字符串类型
    NUMBER = "NUMBER"      # 数字类型（浮点数）
    INTEGER = "INTEGER"    # 整数类型
    BOOLEAN = "BOOLEAN"    # 布尔类型
    ARRAY = "ARRAY"        # 数组类型
    OBJECT = "OBJECT"      # 对象类型


class JsonSchemaType:
    """
    JSON Schema类型常量

    定义JSON Schema规范中的标准类型。
    用于将业务参数类型转换为标准Schema类型。
    """
    STRING = "string"      # 字符串
    NUMBER = "number"      # 数字
    BOOLEAN = "boolean"    # 布尔值
    ARRAY = "array"        # 数组
    OBJECT = "object"      # 对象


class RequestType:
    """
    请求类型常量

    定义API参数在HTTP请求中的位置类型。
    用于将参数正确分组到请求的不同部分。
    """
    HEADER = "header"              # 请求头参数
    QUERY = "query"                # 查询参数（URL参数）
    BODY = "body"                  # 请求体参数（JSON格式）
    FORM = "form"                  # 表单参数（application/x-www-form-urlencoded）
    FORMDATA = "formdata"          # 多部分表单参数（multipart/form-data，支持文件上传）
    PATH = "path"                  # 路径参数
    PARAMS = "params"              # 路径参数（别名）
    LZWCAI_CONFIG = "lzwcaiConfig" # lzwcaiConfig参数（新的用户ID存储位置）


# ==================== 类型映射配置 ====================

# 参数类型映射表：业务参数类型 -> JSON Schema类型
PARAM_TYPE_MAPPING = {
    ParamType.STRING: JsonSchemaType.STRING,    # 字符串 -> string
    ParamType.NUMBER: JsonSchemaType.NUMBER,    # 数字 -> number
    ParamType.INTEGER: JsonSchemaType.NUMBER,   # 整数 -> number（JSON Schema中整数也是number）
    ParamType.BOOLEAN: JsonSchemaType.BOOLEAN,  # 布尔 -> boolean
    ParamType.ARRAY: JsonSchemaType.ARRAY,      # 数组 -> array
    ParamType.OBJECT: JsonSchemaType.OBJECT,    # 对象 -> object
}

# ==================== 默认参数配置 ====================

# 默认用户ID参数配置
# 这个参数会自动添加到所有API的Schema中，用于标识当前用户
DEFAULT_USER_ID_PARAM = {
    "paramName": "userId",                                    # 参数名称
    "paramType": ParamType.STRING,                           # 参数类型：字符串
    "paramPrompts": "当前与您对话的用户信息的用户ID",          # 参数描述
    "requestType": RequestType.LZWCAI_CONFIG,                # 请求类型：lzwcaiConfig类型
    "required": 1,                                           # 必填参数
}


# ==================== 核心函数 ====================

def generate_json_schema(api_params: List[Dict[str, Any]]) -> Dict[str, Any]:
    """
    API参数列表转JSON Schema格式

    这是模块的核心函数，负责将业务平台的API参数列表转换为符合JSON Schema规范的对象。
    生成的Schema用于MCP工具的参数定义和验证。

    转换流程:
    1. 验证输入参数类型
    2. 过滤掉header类型的参数（header参数单独处理）
    3. 添加默认的userId参数（存储在lzwcaiConfig中）
    4. 按请求类型分组参数（query, body, lzwcaiConfig等）
    5. 为每个分组创建Schema属性
    6. 清理空的required列表

    Schema结构:
    {
        "type": "object",
        "properties": {
            "query": {
                "type": "object",
                "properties": {...},
                "required": [...]
            },
            "body": {
                "type": "object",
                "properties": {...},
                "required": [...]
            },
            "lzwcaiConfig": {
                "type": "object",
                "properties": {
                    "userId": {
                        "type": "string",
                        "description": "当前与您对话的用户信息的用户ID"
                    }
                },
                "required": ["userId"]
            }
        },
        "required": ["query", "body", "lzwcaiConfig"]  // 只包含有参数的分组
    }

    参数:
        api_params: API参数对象列表，每个对象包含：
            - paramName: 参数名称
            - paramType: 参数类型（STRING, NUMBER等）
            - paramPrompts: 参数描述
            - requestType: 请求类型（header, query, body等）
            - required: 是否必填（1为必填，0为可选）
            - defaultValue: 默认值（可选）

    返回:
        dict: 符合JSON Schema规范的对象

    异常处理:
        TypeError: 如果api_params不是列表类型

    设计考虑:
        - header参数被过滤掉，因为它们在HTTP请求中单独处理
        - 自动添加userId参数到lzwcaiConfig分组，确保所有API都能获取用户信息
        - 按请求类型分组，便于后续的参数处理和验证
        - 清理空的required列表，保持Schema的简洁性
    """
    # 参数类型验证
    if not isinstance(api_params, list):
        raise TypeError("api_params must be a list")

    logger.debug(f"生成JSON Schema，参数数量: {len(api_params)}")

    # 创建基础Schema结构
    schema = {
        "type": JsonSchemaType.OBJECT,  # 根类型为对象
        "properties": {},               # 属性定义
        "required": []                  # 必填字段列表
    }

    # 过滤参数并添加默认userId参数
    # header参数在HTTP请求中单独处理，不包含在Schema中
    filtered_params = [
        param for param in api_params
        if param.get("requestType") != RequestType.HEADER
    ]

    # 添加默认的userId参数到lzwcaiConfig分组，确保所有API都能获取用户信息
    filtered_params.append(DEFAULT_USER_ID_PARAM)

    logger.debug(f"过滤后参数数量: {len(filtered_params)}")

    # 按请求类型分组参数
    param_groups = _group_parameters_by_type(filtered_params)
    logger.debug(f"参数分组: {list(param_groups.keys())}")

    # 为每个请求类型创建Schema属性
    for req_type, params in param_groups.items():
        logger.debug(f"处理参数组 {req_type}，包含 {len(params)} 个参数")
        _add_request_type_to_schema(schema, req_type, params)

    # 清理空的required列表，保持Schema简洁
    _cleanup_empty_required_lists(schema)

    logger.debug("JSON Schema生成完成")
    return schema


# ==================== 辅助函数 ====================

def _group_parameters_by_type(
    params: List[Dict[str, Any]],
) -> Dict[str, List[Dict[str, Any]]]:
    """
    按请求类型分组参数

    将参数列表按照requestType字段进行分组，便于后续按类型处理。
    如果参数没有指定requestType，默认归类为query类型。

    参数:
        params: 参数字典列表

    返回:
        Dict[str, List[Dict[str, Any]]]: 按类型分组的参数字典
        格式: {"query": [...], "body": [...], "lzwcaiConfig": [...]}
    """
    param_groups = {}
    for param in params:
        # 获取请求类型，默认为query
        req_type = param.get("requestType", RequestType.QUERY)
        
        # 记录参数名和类型，便于调试
        param_name = param.get("paramName", "未命名参数")
        logger.debug(f"参数 '{param_name}' 归类为 {req_type} 类型")

        # 初始化分组
        if req_type not in param_groups:
            param_groups[req_type] = []
            logger.debug(f"创建新的参数分组: {req_type}")

        # 添加参数到对应分组
        param_groups[req_type].append(param)

    logger.debug(f"参数分组完成，共 {len(param_groups)} 个分组")
    return param_groups


def _add_request_type_to_schema(
    schema: Dict[str, Any], req_type: str, params: List[Dict[str, Any]]
) -> None:
    """
    向Schema添加请求类型分组

    为指定的请求类型创建Schema属性，并处理该类型下的所有参数。
    每个请求类型都会成为根Schema的一个属性。

    参数:
        schema: 根Schema对象
        req_type: 请求类型（如"query", "body", "lzwcaiConfig"）
        params: 该类型下的参数列表
    """
    # 如果该请求类型还没有在Schema中定义，创建它
    if req_type not in schema["properties"]:
        schema["properties"][req_type] = {
            "type": JsonSchemaType.OBJECT,  # 每个请求类型都是对象类型
            "properties": {},               # 该类型下的参数定义
            "required": [],                 # 该类型下的必填参数
        }

    # 如果该类型有参数，将其标记为根级别的必填字段
    if params:
        schema["required"].append(req_type)

    # 处理该类型下的每个参数
    for param in params:
        _add_parameter_to_schema(schema["properties"][req_type], param)


def _add_parameter_to_schema(
    type_schema: Dict[str, Any], param: Dict[str, Any]
) -> None:
    """
    向类型Schema添加单个参数

    将单个参数的定义添加到指定类型的Schema中，包括参数类型、描述、
    默认值、示例等信息。

    参数处理:
    1. 提取参数基本信息（名称、类型、描述）
    2. 处理默认值（添加到描述中并设置default字段）
    3. 映射参数类型到JSON Schema类型
    4. 创建参数定义对象
    5. 添加到Schema的properties中
    6. 处理必填参数标记

    参数:
        type_schema: 类型Schema对象（如query、body的Schema）
        param: 参数定义字典
    """
    # 获取参数名称，如果没有名称则跳过
    param_name = param.get("paramName")
    if not param_name:
        logger.warning(f"参数缺少名称，跳过: {param}")
        return

    # 获取参数类型和描述
    param_type = param.get("paramType", ParamType.STRING)
    param_desc = param.get("paramPrompts", "")
    is_required = param.get("required") == 1
    
    logger.debug(f"添加参数: {param_name}, 类型: {param_type}, 必填: {is_required}")

    # 如果有默认值，添加到描述中
    if param.get("defaultValue") is not None:
        param_desc += f"（默认值为{param['defaultValue']}）"
        logger.debug(f"参数 {param_name} 有默认值: {param['defaultValue']}")

    # 将业务参数类型映射到JSON Schema类型
    json_type = PARAM_TYPE_MAPPING.get(param_type, JsonSchemaType.STRING)
    if param_type not in PARAM_TYPE_MAPPING:
        logger.warning(f"未知的参数类型 {param_type}，使用默认类型 string")

    # 创建参数定义对象
    param_def = {
        "type": json_type,          # JSON Schema类型
        "description": param_desc,  # 参数描述
    }

    # 添加可选字段
    if param.get("defaultValue") is not None:
        param_def["default"] = param["defaultValue"]  # 默认值
    if param.get("example") is not None:
        param_def["example"] = param["example"]       # 示例值

    # 添加到类型Schema的properties中
    type_schema["properties"][param_name] = param_def

    # 如果是必填参数，添加到required列表中
    if is_required:
        type_schema["required"].append(param_name)
        logger.debug(f"参数 {param_name} 标记为必填")


def _cleanup_empty_required_lists(schema: Dict[str, Any]) -> None:
    """
    清理Schema中的空required列表

    移除Schema中所有空的required数组，保持Schema的简洁性。
    这包括嵌套属性中的required列表和顶级的required列表。

    清理规则:
    1. 遍历所有请求类型的Schema
    2. 如果某个类型的required列表为空，删除该字段
    3. 如果顶级required列表为空，删除该字段

    参数:
        schema: 要清理的Schema对象
    """
    # 清理嵌套属性中的空required列表
    for req_type in list(schema["properties"].keys()):
        type_schema = schema["properties"][req_type]
        if not type_schema.get("required"):
            # 如果required列表为空，删除该字段
            type_schema.pop("required", None)

    # 清理顶级的空required列表
    if not schema.get("required"):
        schema.pop("required", None)


def create_structured_data(
    schema: Dict[str, Any], params: Dict[str, Any]
) -> Dict[str, Any]:
    """
    Generate data structure based on schema and input parameters.

    Automatically matches input parameters to schema fields and creates
    a properly structured data object.

    Args:
        schema: JSON schema object defining the data structure
        params: Parameter values to fill

    Returns:
        dict: Structured data object conforming to schema

    Raises:
        TypeError: If schema or params are not dictionaries
    """
    if not isinstance(schema, dict):
        raise TypeError("schema must be a dictionary")
    if not isinstance(params, dict):
        raise TypeError("params must be a dictionary")

    result = {}

    # Process each top-level schema property
    for field_name, field_schema in schema.get("properties", {}).items():
        result[field_name] = {}
        field_properties = field_schema.get("properties", {})

        # Find matching parameters for this field
        matched_params = {
            param_name: param_value
            for param_name, param_value in params.items()
            if param_name in field_properties
        }

        # Only include field if there are matching parameters
        if matched_params:
            result[field_name] = matched_params

    return result


def generate_schema_prompt(schema: Dict[str, Any]) -> str:
    """
    Generate descriptive prompt from JSON Schema for LLM guidance.

    Args:
        schema: JSON Schema object returned by generate_json_schema

    Returns:
        str: Structured prompt text for guiding LLM parameter generation

    Raises:
        TypeError: If schema is not a dictionary
    """
    if not isinstance(schema, dict):
        raise TypeError("schema must be a dictionary")

    prompt_parts = ["当前工具所需参数：\n"]

    # Process each parameter group
    for group_name, group_schema in schema.get("properties", {}).items():
        prompt_parts.append(f"## {group_name} 参数组：")

        required_params = set(group_schema.get("required", []))

        # Process each parameter in the group
        for param_name, param_info in group_schema.get("properties", {}).items():
            param_type = param_info.get("type", JsonSchemaType.STRING)
            param_desc = param_info.get("description", "")
            required_mark = "（必填）" if param_name in required_params else "（可选）"

            prompt_parts.append(
                f"- {param_name}{required_mark}: {param_desc}, 类型: {param_type}"
            )

        prompt_parts.append("")

    # Add output format guidance
    prompt_parts.extend(
        [
            "参数格式要求：JSON对象，包含所有必填字段。",
            "示例格式:",
            "```json",
            "{",
        ]
    )

    for group_name in schema.get("properties", {}):
        prompt_parts.append(f'  "{group_name}": {{')
        prompt_parts.append("    // 相关参数")
        prompt_parts.append("  },")

    prompt_parts.extend(["}", "```"])

    return "\n".join(prompt_parts)


def _remove_property_from_schema(schema: Dict[str, Any], property_name: str) -> None:
    """
    Remove a property from schema (helper function to reduce code duplication).

    Args:
        schema: Schema object to modify
        property_name: Name of property to remove
    """
    # Remove from properties
    if isinstance(schema.get("properties"), dict):
        schema["properties"].pop(property_name, None)

    # Remove from required list
    if isinstance(schema.get("required"), list):
        try:
            schema["required"].remove(property_name)
        except ValueError:
            pass  # Property not in required list, ignore

        # Remove empty required list
        if not schema["required"]:
            schema.pop("required", None)


def remove_property_from_api_item(
    api_item: Dict[str, Any], property_name: str
) -> Dict[str, Any]:
    """
    Remove specified property from a single API object.

    Args:
        api_item: API object (single element from generate_api_array result)
        property_name: Name of property to remove

    Returns:
        dict: Processed API object with property removed

    Raises:
        ValueError: When input parameters are invalid
        TypeError: When input types are incorrect
    """
    if not isinstance(api_item, dict) or not api_item:
        raise ValueError("api_item must be a non-empty dictionary")

    if not isinstance(property_name, str) or not property_name.strip():
        raise ValueError("property_name must be a non-empty string")

    # Create deep copy to avoid modifying original
    new_api = copy.deepcopy(api_item)

    if "schema" in new_api and isinstance(new_api["schema"], dict):
        _remove_property_from_schema(new_api["schema"], property_name)

    return new_api


def remove_property_from_api_array(
    api_array: List[Dict[str, Any]], property_name: str
) -> List[Dict[str, Any]]:
    """
    Remove specified property from all API objects in array.

    Args:
        api_array: API array returned by generate_api_array
        property_name: Name of property to remove

    Returns:
        list: Processed API array with property removed from all items

    Raises:
        ValueError: When input parameters are invalid
        TypeError: When input types are incorrect
    """
    if not isinstance(api_array, list) or not api_array:
        raise ValueError("api_array must be a non-empty list")

    if not isinstance(property_name, str) or not property_name.strip():
        raise ValueError("property_name must be a non-empty string")

    result = []

    for api_item in api_array:
        try:
            processed_item = remove_property_from_api_item(api_item, property_name)
            result.append(processed_item)
        except Exception as e:
            result.append(copy.deepcopy(api_item))  # Keep original if processing fails

    return result


def fill_default_values_by_schema(
    schema: Dict[str, Any], arguments: Dict[str, Any]
) -> Dict[str, Any]:
    """
    Auto-fill default values in arguments based on JSON Schema.

    Only fills missing parameters or those with None/empty string values.

    Args:
        schema: JSON Schema object with grouped structure (body/query/lzwcaiConfig)
        arguments: User input parameter dictionary

    Returns:
        dict: Parameter dictionary with default values filled

    Raises:
        TypeError: When input types are incorrect
    """
    if not isinstance(schema, dict):
        raise TypeError("schema must be a dictionary")

    if not isinstance(arguments, dict):
        arguments = {}
    else:
        arguments = copy.deepcopy(arguments)

    for group_name, group_schema in schema.get("properties", {}).items():
        if not isinstance(arguments.get(group_name), dict):
            arguments[group_name] = {}

        for param_name, param_schema in group_schema.get("properties", {}).items():
            default_value = param_schema.get("default")
            current_value = arguments[group_name].get(param_name)

            # Fill default only if parameter is missing, None, or empty string
            if (
                current_value is None or current_value == ""
            ) and default_value is not None:
                arguments[group_name][param_name] = default_value

    return arguments


def generate_api_array(api_params: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """
    Generate API array with JSON Schema for each API.

    Args:
        api_params: List of API parameter definitions

    Returns:
        list: API array with schema attached to each item

    Raises:
        TypeError: If api_params is not a list
    """
    if not isinstance(api_params, list):
        raise TypeError("api_params must be a list")

    api_array = []
    for param in api_params:
        try:
            schema = generate_json_schema(param.get("parameters", []))
            api_array.append({**param, "schema": schema})
        except Exception as e:
            logger.error(f"Error processing config: {str(e)}")
            api_array.append(param)  # Add without schema if generation fails

    return api_array


def check_required_arguments(
    schema: Dict[str, Any], arguments: Dict[str, Any]
) -> List[str]:
    """
    Validate that arguments contain all required parameters from schema.

    Args:
        schema: JSON Schema object with grouped structure (body/query/lzwcaiConfig)
        arguments: User input parameter dictionary

    Returns:
        list: Missing required parameters with group names, e.g., ['body.username', 'lzwcaiConfig.userId']

    Raises:
        TypeError: When input types are incorrect
    """
    if not isinstance(schema, dict):
        raise TypeError("schema must be a dictionary")

    if not isinstance(arguments, dict):
        arguments = {}

    missing_params = []

    for group_name, group_schema in schema.get("properties", {}).items():
        group_args = arguments.get(group_name, {})
        required_params = group_schema.get("required", [])

        for param_name in required_params:
            param_value = group_args.get(param_name) if group_args else None

            # Consider missing if not provided, None, or empty string
            if param_value is None or param_value == "":
                # Try to get parameter description
                description = ""
                try:
                    param_properties = group_schema.get("properties", {})
                    param_info = param_properties.get(param_name, {})
                    description = param_info.get("description", "")
                except Exception:
                    pass

                # Format missing parameter name
                if description:
                    missing_params.append(f"{group_name}.{param_name}（{description}）")
                else:
                    missing_params.append(f"{group_name}.{param_name}")

    return missing_params


if __name__ == "__main__":
    # Example usage and testing
    try:
        with open(
            "E:/yh-ai/project/lzwcai-szyg/lzwcai-demp-tool-server/src/"
            "lzwcai_demp_tool_server_business_to_mcp/mcp_generator/src/parameters.json",
            "r",
            encoding="utf-8",
        ) as f:
            api_params = json.load(f)

        api_array = generate_api_array(api_params)
        result = remove_property_from_api_array(api_array, "userId")

        # Write results to JSON file
        with open("schema1.json", "w", encoding="utf-8") as f:
            json.dump(result, f, ensure_ascii=False, indent=4)

    except FileNotFoundError:
        logger.error(f"Warning: Test data file not found. Skipping example execution.")
    except Exception as e:
        logger.error(f"Error in example execution: {str(e)}")
