package tech.easyflow.agent.config;

import org.springframework.stereotype.Component;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.system.service.CategoryPermissionService;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Agent 内置工具配置的默认值、兼容和权限统一解析器。
 */
@Component
public class AgentBuiltinToolsConfigResolver {

    /** executionConfigJson 中的内置工具字段。 */
    public static final String BUILTIN_TOOLS_KEY = "builtinTools";
    /** 超级管理员关闭 Shell 审批时提交的一次性确认字段。 */
    public static final String SHELL_RISK_CONFIRMATION_KEY = "shellApprovalRiskConfirmed";

    private final CategoryPermissionService categoryPermissionService;

    /**
     * 创建解析器。
     *
     * @param categoryPermissionService 平台超级管理员判定服务
     */
    public AgentBuiltinToolsConfigResolver(CategoryPermissionService categoryPermissionService) {
        this.categoryPermissionService = categoryPermissionService;
    }

    /**
     * 为草稿详情补齐展示默认值，但不直接写回数据库。
     *
     * @param source 原执行配置
     * @return 带完整五项配置的副本
     */
    public Map<String, Object> normalizeForDraftRead(Map<String, Object> source) {
        return replaceBuiltinTools(source, parse(source, AgentBuiltinToolsConfig.newAgentDefaults()));
    }

    /**
     * 规范化一次显式草稿保存，并校验关闭 Shell 审批的权限与风险确认。
     *
     * @param source 客户端提交的执行配置
     * @param existingSource 更新前执行配置；新建时为 null
     * @param account 当前账号
     * @return 可持久化且已移除一次性确认字段的配置
     */
    public Map<String, Object> normalizeForDraftSave(Map<String, Object> source,
                                                     Map<String, Object> existingSource,
                                                     LoginAccount account) {
        AgentBuiltinToolsConfig incoming = parse(source, AgentBuiltinToolsConfig.newAgentDefaults());
        AgentBuiltinToolsConfig existing = existingSource == null
                ? AgentBuiltinToolsConfig.newAgentDefaults()
                : parse(existingSource, AgentBuiltinToolsConfig.newAgentDefaults());
        boolean disablesShellApproval = disablesShellApproval(incoming, existing);
        if (disablesShellApproval) {
            if (!categoryPermissionService.isSuperAdmin(account)) {
                throw new BusinessException(403, 403, "仅平台超级管理员可以关闭 Shell 调用前确认");
            }
            if (!riskConfirmed(source)) {
                throw new BusinessException(400, 400, "关闭 Shell 调用前确认前必须完成高风险确认");
            }
        }
        return replaceBuiltinTools(source, incoming);
    }

    /**
     * 判断保存前后是否真实发生了 Shell 审批关闭变更。
     *
     * @param source 已规范化或待保存的执行配置
     * @param existingSource 保存前执行配置；新建时为 null
     * @return 从需审批或禁用状态切换到启用且免审批时为 true
     */
    public boolean isShellApprovalDisableTransition(Map<String, Object> source,
                                                    Map<String, Object> existingSource) {
        AgentBuiltinToolsConfig incoming = parse(source, AgentBuiltinToolsConfig.newAgentDefaults());
        AgentBuiltinToolsConfig existing = existingSource == null
                ? AgentBuiltinToolsConfig.newAgentDefaults()
                : parse(existingSource, AgentBuiltinToolsConfig.newAgentDefaults());
        return disablesShellApproval(incoming, existing);
    }

    /**
     * 解析草稿运行配置；缺失时使用新 Agent 默认值。
     *
     * @param source 执行配置
     * @return 类型化配置
     */
    public AgentBuiltinToolsConfig resolveDraftRuntime(Map<String, Object> source) {
        return parse(source, AgentBuiltinToolsConfig.newAgentDefaults());
    }

    /**
     * 解析发布快照；旧快照缺失内置工具字段时全部禁用。
     *
     * @param source 发布快照中的执行配置
     * @return 类型化配置
     */
    public AgentBuiltinToolsConfig resolvePublishedRuntime(Map<String, Object> source) {
        return parse(source, AgentBuiltinToolsConfig.allDisabled());
    }

    /**
     * 规范化发布运行配置；旧快照缺失字段时显式写入五项禁用结果。
     *
     * @param source 发布快照执行配置
     * @return 无静默扩权的完整配置副本
     */
    public Map<String, Object> normalizeForPublishedRuntime(Map<String, Object> source) {
        return replaceBuiltinTools(source, resolvePublishedRuntime(source));
    }

    /**
     * 判断执行配置是否显式包含内置工具结构。
     *
     * @param source 执行配置
     * @return 包含时为 true
     */
    public boolean hasBuiltinTools(Map<String, Object> source) {
        return source != null && source.containsKey(BUILTIN_TOOLS_KEY);
    }

    private Map<String, Object> replaceBuiltinTools(Map<String, Object> source,
                                                    AgentBuiltinToolsConfig config) {
        Map<String, Object> result = source == null ? new LinkedHashMap<>() : new LinkedHashMap<>(source);
        result.put(BUILTIN_TOOLS_KEY, config.toMap());
        return result;
    }

    private AgentBuiltinToolsConfig parse(Map<String, Object> source, AgentBuiltinToolsConfig fallback) {
        if (source == null || !source.containsKey(BUILTIN_TOOLS_KEY)) {
            return fallback;
        }
        Map<String, Object> raw = requireMap(source.get(BUILTIN_TOOLS_KEY), "builtinTools 必须为对象");
        validateSchemaVersion(raw.get("schemaVersion"));
        return new AgentBuiltinToolsConfig(
                tool(raw, "read", fallback.read()),
                tool(raw, "write", fallback.write()),
                tool(raw, "patch", fallback.patch()),
                tool(raw, "shell", fallback.shell()),
                tool(raw, "artifactPublish", fallback.artifactPublish()));
    }

    private boolean disablesShellApproval(AgentBuiltinToolsConfig incoming,
                                          AgentBuiltinToolsConfig existing) {
        return incoming.shell().enabled()
                && !incoming.shell().approvalRequired()
                && (existing.shell().approvalRequired() || !existing.shell().enabled());
    }

    private void validateSchemaVersion(Object value) {
        if (value == null) {
            return;
        }
        if (!(value instanceof Number number)
                || number.doubleValue() != number.intValue()
                || number.intValue() != AgentBuiltinToolsConfig.SCHEMA_VERSION) {
            throw new BusinessException("不支持的 Agent 内置工具配置版本");
        }
    }

    private AgentBuiltinToolsConfig.ToolSwitch tool(Map<String, Object> source,
                                                    String key,
                                                    AgentBuiltinToolsConfig.ToolSwitch fallback) {
        if (!source.containsKey(key)) {
            return fallback;
        }
        Map<String, Object> raw = requireMap(source.get(key), "Agent 内置工具项必须为对象: " + key);
        return new AgentBuiltinToolsConfig.ToolSwitch(
                booleanValue(raw, "enabled", fallback.enabled()),
                booleanValue(raw, "approvalRequired", fallback.approvalRequired()));
    }

    private boolean riskConfirmed(Map<String, Object> source) {
        Map<String, Object> raw = mapValue(source == null ? null : source.get(BUILTIN_TOOLS_KEY));
        return raw != null && Boolean.TRUE.equals(raw.get(SHELL_RISK_CONFIRMATION_KEY));
    }

    private boolean booleanValue(Map<String, Object> source, String key, boolean fallback) {
        if (!source.containsKey(key)) {
            return fallback;
        }
        Object value = source.get(key);
        if (value instanceof Boolean bool) {
            return bool;
        }
        throw new BusinessException("Agent 内置工具开关必须为布尔值");
    }

    private Map<String, Object> requireMap(Object value, String message) {
        Map<String, Object> mapped = mapValue(value);
        if (mapped == null) {
            throw new BusinessException(message);
        }
        return mapped;
    }

    private Map<String, Object> mapValue(Object value) {
        if (!(value instanceof Map<?, ?> raw)) {
            return null;
        }
        Map<String, Object> result = new LinkedHashMap<>();
        raw.forEach((key, item) -> result.put(String.valueOf(key), item));
        return result;
    }
}
