# coding=utf-8
"""执行器组装与会话合并的通用工具函数。

本模块提供 job 编排层所需的纯逻辑操作，不依赖任何外部服务或数据库。
主要包括：
- merge_conversation: 标准会话合并（数据库会话 + fallback 会话）
- merge_settings: BusinessSetting 融合
- merge_executor_overrides: 将内联覆盖项合并到基础执行器
- build_inline_executor: 基于内联组件构建临时执行器
"""
from __future__ import annotations

from typing import Any, Dict, List, Optional, Union

from loguru import logger
from pydantic import BaseModel

from turbo_agent_core.schema.agents import Agent, Character
from turbo_agent_core.schema.enums import RunType
from turbo_agent_core.schema.jobs import InlineExecutorConfig
from turbo_agent_core.schema.resources import BusinessSetting
from turbo_agent_core.schema.states import Conversation, Message
from turbo_agent_core.utils.identity import build_inline_turbo_entity_data


def merge_conversation(
    stored_conversation: Conversation,
    fallback_conversation: Conversation,
) -> Conversation:
    """标准会话合并：将数据库加载的会话与 fallback 会话合并。

    规则：
    - 若数据库会话无消息但 fallback 有，则使用 fallback 的消息列表
    - 若数据库会话有消息，忽略 fallback 中数据库不存在的消息（记录警告）
    - 补全 root_message_id 和 root_user_id
    """
    if not stored_conversation.messages and fallback_conversation.messages:
        stored_conversation.messages = list(fallback_conversation.messages)
    else:
        existing_ids = {message.id for message in stored_conversation.messages}
        skipped_message_ids = [
            message.id
            for message in fallback_conversation.messages
            if message.id not in existing_ids
        ]
        if skipped_message_ids:
            logger.warning(
                "会话合并忽略未落库消息，conversation_id={}, message_ids={}",
                stored_conversation.id,
                skipped_message_ids,
            )

    if not stored_conversation.root_message_id and stored_conversation.messages:
        stored_conversation.root_message_id = stored_conversation.messages[0].id

    if not stored_conversation.root_user_id:
        stored_conversation.root_user_id = fallback_conversation.root_user_id

    return stored_conversation


def merge_settings(
    base_setting: Optional[BusinessSetting],
    overlay_setting: Optional[BusinessSetting],
) -> Optional[BusinessSetting]:
    """融合两个 BusinessSetting：target 拼接（旧在前，新在后），principles/knowledges 扩展。"""
    if not overlay_setting:
        return base_setting
    if not base_setting:
        return overlay_setting

    base_target = (base_setting.target or "").strip()
    overlay_target = (overlay_setting.target or "").strip()
    if base_target and overlay_target:
        merged_target = f"{base_target}\n{overlay_target}"
    else:
        merged_target = base_target or overlay_target

    merged_principles = list(base_setting.principles) + list(overlay_setting.principles)
    merged_knowledges = list(base_setting.knowledges) + list(overlay_setting.knowledges)

    return base_setting.model_copy(update={
        "target": merged_target or None,
        "principles": merged_principles,
        "knowledges": merged_knowledges,
    })


def merge_executor_overrides(
    base: Union[Agent, Character],
    inline_config: InlineExecutorConfig,
    *,
    model: Any = None,
    setting: Optional[BusinessSetting] = None,
) -> Union[Agent, Character]:
    """将内联配置中明确指定的字段覆盖到基础执行器上（保留基础执行器的身份）。

    参数:
        base: 基础 Agent/Character（通过 executor_id 从存储获取）
        inline_config: 内联覆盖配置
        model: 已解析的覆盖模型（由调用方预先通过 RuntimeStore 获取并鉴权）
        setting: 已解析的覆盖设定（由调用方预先通过 RuntimeStore 获取并鉴权）

    规则:
        - model: 若已提供解析后的模型，则覆盖基础模型
        - setting: 若已提供解析后的设定，则与基础设定融合（target 拼接，principles/knowledges 扩展）
        - hasMemory / expose_inner_thought: 直接覆盖（仅当 inline_config 中明确指定时）
    """
    updates: Dict[str, Any] = {}

    # 模型覆盖
    if model is not None:
        updates["model"] = model
        logger.info(
            "执行器覆盖模型: base={}, model={}",
            getattr(base, "name", "unknown"),
            getattr(model, "name", "unknown"),
        )

    # Setting 融合
    if setting is not None:
        updates["setting"] = merge_settings(base.setting, setting)
        logger.info("执行器覆盖 Setting 融合完成")

    # 布尔开关
    if inline_config.hasMemory is not None and hasattr(base, "hasMemory"):
        updates["hasMemory"] = inline_config.hasMemory
    if inline_config.expose_inner_thought is not None and hasattr(base, "expose_inner_thought"):
        updates["expose_inner_thought"] = inline_config.expose_inner_thought

    if updates:
        updates["is_inline"] = True
        logger.info("执行器覆盖字段: {}", list(updates.keys()))
        return base.model_copy(update=updates)

    return base


def build_inline_executor(
    inline_config: InlineExecutorConfig,
    *,
    model: Any = None,
    tools: Optional[list] = None,
    setting: Optional[BusinessSetting] = None,
    space_name: Optional[str] = None,
    project_name_id: Optional[str] = None,
) -> Union[Agent, Character]:
    """基于内联组件从零构建临时执行器。

    参数:
        inline_config: 内联执行器配置
        model: 已解析的可运行模型
        tools: 已解析的可运行工具列表
        setting: 已解析的业务设定
        space_name: 空间名（可从 inline_config 中推断）
        project_name_id: 项目名（可从 inline_config 中推断）

    返回:
        构造完成的 Agent 或 Character 实例（is_inline=True）
    """
    resolved_space = space_name
    resolved_project = project_name_id

    # 尝试从 inline_config 推断 space/project
    if inline_config.belong_to_project_path and "/" in inline_config.belong_to_project_path:
        parts = inline_config.belong_to_project_path.split("/", 1)
        resolved_space = resolved_space or parts[0]
        resolved_project = resolved_project or parts[1]

    # 尝试从模型推断 space
    if not resolved_space and model is not None:
        resolved_space = getattr(model, "space_name", None)

    if not resolved_space:
        raise ValueError("内联执行器缺少可解析的 space_name，无法构造临时执行器")

    resolved_project_path = (
        inline_config.belong_to_project_path
        or (f"{resolved_space}/{resolved_project}" if resolved_project else None)
    )

    common_data = build_inline_turbo_entity_data(
        entity_id=inline_config.id,
        name=inline_config.name,
        name_id=inline_config.name_id,
        space_name=resolved_space,
        project_name_id=resolved_project,
        belong_to_project_path=resolved_project_path,
        description=inline_config.description,
        avatar_uri=inline_config.avatar_uri,
        run_type=(
            RunType.Character.value
            if inline_config.run_type == RunType.Character
            else RunType.AGENT.value
        ),
        model=model,
        setting=setting,
        tools=tools or [],
    )

    if inline_config.run_type == RunType.Character:
        return Character.model_validate({**common_data, "is_inline": True})

    return Agent.model_validate({
        **common_data,
        "is_inline": True,
        "hasMemory": inline_config.hasMemory if inline_config.hasMemory is not None else False,
        "expose_inner_thought": (
            inline_config.expose_inner_thought
            if inline_config.expose_inner_thought is not None
            else True
        ),
    })
