package tech.easyflow.ai.mcp;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import tech.easyflow.ai.entity.Mcp;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HexFormat;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * 构建可持久化的 MCP 受控连接快照。
 *
 * <p>连接拓扑可冻结，凭据值只能使用 {@code ${input:key}} 服务端引用，
 * 避免发布与审批快照复制已经解析的令牌、Header 或查询参数。</p>
 */
@Component
public class McpConnectionSnapshotFactory {

    private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() { };
    private static final Pattern INPUT_REFERENCE =
            Pattern.compile("^\\$\\{input:[A-Za-z0-9_.-]+}$");
    private static final Pattern SENSITIVE_NAME = Pattern.compile(
            ".*(token|secret|password|passwd|api[_-]?key|authorization|cookie|credential|private[_-]?key).*",
            Pattern.CASE_INSENSITIVE);
    private static final Set<String> PUBLIC_HEADERS = Set.of(
            "accept", "accept-language", "content-type", "user-agent");

    private final ObjectMapper objectMapper;

    /**
     * 创建快照工厂。
     *
     * @param objectMapper JSON 映射器
     */
    public McpConnectionSnapshotFactory(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    /**
     * 构建字段白名单 MCP 连接快照并校验凭据引用。
     *
     * @param mcp MCP 资源
     * @return 仅供服务端 Runtime 使用的连接快照
     * @throws BusinessException 配置包含明文凭据或格式无效时抛出
     */
    public Map<String, Object> snapshot(Mcp mcp) {
        if (mcp == null || mcp.getId() == null) {
            throw new BusinessException("MCP 资源不能为空");
        }
        validateCredentialReferences(mcp.getConfigJson());
        Map<String, Object> snapshot = new LinkedHashMap<>();
        snapshot.put("id", mcp.getId());
        snapshot.put("title", mcp.getTitle());
        snapshot.put("description", mcp.getDescription());
        snapshot.put("transportType", mcp.getTransportType());
        snapshot.put("approvalRequired", Boolean.TRUE.equals(mcp.getApprovalRequired()));
        snapshot.put("configJson", mcp.getConfigJson());
        snapshot.put("configHash", sha256(mcp.getConfigJson()));
        return snapshot;
    }

    private void validateCredentialReferences(String configJson) {
        if (configJson == null || configJson.isBlank()) {
            throw new BusinessException("MCP 配置 JSON 不能为空");
        }
        Map<String, Object> config;
        try {
            config = objectMapper.readValue(configJson, MAP_TYPE);
        } catch (Exception exception) {
            throw new BusinessException("MCP 配置 JSON 格式错误");
        }
        Map<String, Object> servers = map(config.get("mcpServers"), "mcpServers");
        for (Map.Entry<String, Object> entry : servers.entrySet()) {
            Map<String, Object> server = map(entry.getValue(), "MCP 服务 " + entry.getKey());
            validateMap(server.get("headers"), "headers", true);
            validateMap(server.get("queryParams"), "queryParams", true);
            validateMap(server.get("env"), "env", false);
            validateUrl(server.get("url"));
            validateArgs(server.get("args"));
            validateNestedSensitiveValues(server, "mcpServers." + entry.getKey());
        }
    }

    private void validateMap(Object value, String field, boolean requireReferenceForAll) {
        if (value == null) {
            return;
        }
        Map<String, Object> values = map(value, field);
        for (Map.Entry<String, Object> entry : values.entrySet()) {
            String key = entry.getKey();
            String text = entry.getValue() == null ? "" : String.valueOf(entry.getValue()).trim();
            boolean publicHeader = "headers".equals(field)
                    && PUBLIC_HEADERS.contains(key.toLowerCase(Locale.ROOT));
            boolean requiresReference = (requireReferenceForAll && !publicHeader)
                    || SENSITIVE_NAME.matcher(key).matches();
            if (requiresReference && !text.isEmpty() && !INPUT_REFERENCE.matcher(text).matches()) {
                throw new BusinessException("MCP " + field + " 中的凭据必须使用 ${input:key} 引用：" + key);
            }
        }
    }

    private void validateUrl(Object value) {
        if (value == null) {
            return;
        }
        String lower = String.valueOf(value).toLowerCase(Locale.ROOT);
        if (lower.matches(".*[?&](token|secret|password|api[_-]?key|authorization)=[^&$][^&]*.*")
                || lower.matches("^[a-z][a-z0-9+.-]*://[^/@]+:[^/@]+@.*")) {
            throw new BusinessException("MCP URL 不能包含明文凭据，请使用 ${input:key} 引用");
        }
    }

    private void validateArgs(Object value) {
        if (!(value instanceof List<?> args)) {
            return;
        }
        for (int index = 0; index < args.size(); index++) {
            Object raw = args.get(index);
            String arg = raw == null ? "" : String.valueOf(raw);
            if (!SENSITIVE_NAME.matcher(arg).matches()) {
                continue;
            }
            int separator = arg.indexOf('=');
            if (separator >= 0 && INPUT_REFERENCE.matcher(arg.substring(separator + 1).trim()).matches()) {
                continue;
            }
            if (separator < 0 && index + 1 < args.size()
                    && INPUT_REFERENCE.matcher(String.valueOf(args.get(index + 1)).trim()).matches()) {
                index++;
                continue;
            }
            throw new BusinessException("MCP 启动参数不能包含明文凭据，请使用 ${input:key} 引用");
        }
    }

    /**
     * 递归检查扩展配置，防止未知或嵌套敏感字段绕过固定字段校验。
     *
     * @param value 当前配置值
     * @param path 配置路径
     */
    private void validateNestedSensitiveValues(Object value, String path) {
        if (value instanceof Map<?, ?> values) {
            for (Map.Entry<?, ?> entry : values.entrySet()) {
                String key = String.valueOf(entry.getKey());
                Object nested = entry.getValue();
                String nestedPath = path + "." + key;
                if (SENSITIVE_NAME.matcher(key).matches()
                        && (nested == null || !INPUT_REFERENCE.matcher(String.valueOf(nested).trim()).matches())) {
                    throw new BusinessException("MCP 敏感配置必须使用 ${input:key} 引用：" + nestedPath);
                }
                if (!SENSITIVE_NAME.matcher(key).matches()) {
                    validateNestedSensitiveValues(nested, nestedPath);
                }
            }
            return;
        }
        if (value instanceof List<?> values) {
            for (int index = 0; index < values.size(); index++) {
                validateNestedSensitiveValues(values.get(index), path + "[" + index + "]");
            }
        }
    }

    private Map<String, Object> map(Object value, String field) {
        if (!(value instanceof Map<?, ?> raw)) {
            throw new BusinessException("MCP 配置字段必须是对象：" + field);
        }
        Map<String, Object> result = new LinkedHashMap<>();
        raw.forEach((key, item) -> result.put(String.valueOf(key), item));
        return result;
    }

    private String sha256(String value) {
        try {
            return HexFormat.of().formatHex(MessageDigest.getInstance("SHA-256")
                    .digest(value.getBytes(StandardCharsets.UTF_8)));
        } catch (NoSuchAlgorithmException exception) {
            throw new IllegalStateException("SHA-256 is unavailable", exception);
        }
    }
}
