package tech.easyflow.approval.support;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

/**
 * 审批详情快照的响应投影。
 *
 * <p>审批实例仍持久化完整冻结快照，详情响应仅投影审核需要的摘要字段，避免内部运行正文、
 * 资源快照和连接凭据通过管理端查询接口泄露。</p>
 */
public final class ApprovalSnapshotProjection {

    private static final Set<String> STRICT_RESOURCE_TYPES = Set.of("AGENT", "SKILL");
    private static final Set<String> REMOVED_KEYS = Set.of(
            "skillcontent",
            "content",
            "contentref",
            "resources",
            "resourcecontent",
            "publishedsnapshotjson",
            "publishedtoolbindingsjson",
            "prompt",
            "systemprompt",
            "instructions",
            "instruction",
            "script",
            "source",
            "code",
            "body",
            "text",
            "raw",
            "payload",
            "tools",
            "mcptoolmanifest",
            "manifest",
            "headers",
            "header",
            "environment",
            "env",
            "command",
            "args",
            "arguments",
            "query",
            "endpoint",
            "url",
            "uri",
            "credentials",
            "credential",
            "secret",
            "password",
            "token",
            "apikey",
            "privatekey",
            "inputschema",
            "outputschema",
            "schema");

    private ApprovalSnapshotProjection() {
    }

    /**
     * 将审批冻结快照转换为可下发的响应副本。
     *
     * @param resourceType 审批资源类型
     * @param snapshot 完整冻结快照
     * @return 不包含内部正文与连接配置的响应快照；原快照为空时返回空 Map
     */
    public static Map<String, Object> project(String resourceType, Map<String, Object> snapshot) {
        if (snapshot == null || snapshot.isEmpty()) {
            return Map.of();
        }
        if (!STRICT_RESOURCE_TYPES.contains(normalizeResourceType(resourceType))) {
            return copyMap(snapshot);
        }
        Map<String, Object> projected = new LinkedHashMap<>();
        snapshot.forEach((key, value) -> {
            if ("resourceSnapshot".equals(key) && value instanceof Map<?, ?> resourceSnapshot) {
                projected.put(key, projectMap(resourceSnapshot));
                return;
            }
            projected.put(key, copyValue(value));
        });
        return projected;
    }

    /**
     * 递归投影 L21 资源快照。
     *
     * @param source 待投影 Map
     * @return 保持原有顺序的安全摘要 Map
     */
    private static Map<String, Object> projectMap(Map<?, ?> source) {
        Map<String, Object> projected = new LinkedHashMap<>();
        source.forEach((rawKey, value) -> {
            String key = String.valueOf(rawKey);
            String normalizedKey = normalizeKey(key);
            if ("resources".equals(normalizedKey)) {
                putCollectionCount(projected, "resourceCount", value);
                return;
            }
            if ("mcptoolmanifest".equals(normalizedKey) || "manifest".equals(normalizedKey)) {
                putCollectionCount(projected, "manifestToolCount", value);
                return;
            }
            if ("resourcesnapshot".equals(normalizedKey)) {
                return;
            }
            if (isRemovedKey(normalizedKey)) {
                return;
            }
            if (isConfigKey(normalizedKey)) {
                return;
            }
            projected.put(key, projectValue(value));
        });
        return projected;
    }

    /**
     * 递归投影 Map、集合和数组值。
     *
     * @param value 原始值
     * @return 安全副本
     */
    private static Object projectValue(Object value) {
        if (value instanceof Map<?, ?> map) {
            return projectMap(map);
        }
        if (value instanceof Collection<?> collection) {
            List<Object> projected = new ArrayList<>(collection.size());
            collection.forEach(item -> projected.add(projectValue(item)));
            return projected;
        }
        if (value != null && value.getClass().isArray()) {
            int length = Array.getLength(value);
            List<Object> projected = new ArrayList<>(length);
            for (int index = 0; index < length; index++) {
                projected.add(projectValue(Array.get(value, index)));
            }
            return projected;
        }
        return value;
    }

    /**
     * 判断字段是否属于连接配置或其他配置正文。
     *
     * @param normalizedKey 已规范化字段名
     * @return 需要移除时为 true
     */
    private static boolean isConfigKey(String normalizedKey) {
        return normalizedKey.endsWith("config") || normalizedKey.endsWith("configjson")
                || normalizedKey.endsWith("configuration");
    }

    /**
     * 判断字段是否需要从严格响应投影中移除。
     *
     * @param normalizedKey 已规范化字段名
     * @return 需要移除时为 true
     */
    private static boolean isRemovedKey(String normalizedKey) {
        if (normalizedKey.endsWith("hash") || normalizedKey.endsWith("count")) {
            return false;
        }
        return REMOVED_KEYS.contains(normalizedKey)
                || normalizedKey.endsWith("content")
                || normalizedKey.endsWith("contentref")
                || normalizedKey.endsWith("storagepath")
                || normalizedKey.endsWith("physicalpath")
                || normalizedKey.endsWith("source")
                || normalizedKey.contains("manifest")
                || normalizedKey.contains("credential")
                || normalizedKey.endsWith("secret")
                || normalizedKey.endsWith("password")
                || normalizedKey.endsWith("token")
                || normalizedKey.endsWith("apikey")
                || normalizedKey.endsWith("privatekey")
                || normalizedKey.endsWith("schema")
                || normalizedKey.endsWith("headers")
                || normalizedKey.endsWith("environment")
                || normalizedKey.endsWith("env")
                || normalizedKey.endsWith("command")
                || normalizedKey.endsWith("args")
                || normalizedKey.endsWith("arguments")
                || normalizedKey.endsWith("query")
                || normalizedKey.endsWith("queryparams")
                || normalizedKey.endsWith("queryparameters")
                || normalizedKey.endsWith("endpoint")
                || normalizedKey.endsWith("url")
                || normalizedKey.endsWith("uri");
    }

    /**
     * 在源值可计数时写入摘要数量，并避免覆盖已存在的显式数量。
     *
     * @param projected 目标 Map
     * @param countKey 数量字段名
     * @param value 待计数值
     */
    private static void putCollectionCount(Map<String, Object> projected, String countKey, Object value) {
        if (projected.containsKey(countKey)) {
            return;
        }
        if (value instanceof Collection<?> collection) {
            projected.put(countKey, collection.size());
        } else if (value != null && value.getClass().isArray()) {
            projected.put(countKey, Array.getLength(value));
        }
    }

    /**
     * 为不采用严格 L21 投影的历史资源复制快照，避免响应方修改持久化 Map。
     *
     * @param source 原始 Map
     * @return 深复制 Map
     */
    private static Map<String, Object> copyMap(Map<?, ?> source) {
        Map<String, Object> copy = new LinkedHashMap<>();
        source.forEach((key, value) -> copy.put(String.valueOf(key), copyValue(value)));
        return copy;
    }

    /**
     * 深复制 Map、集合和数组。
     *
     * @param value 原始值
     * @return 深复制值
     */
    private static Object copyValue(Object value) {
        if (value instanceof Map<?, ?> map) {
            return copyMap(map);
        }
        if (value instanceof Collection<?> collection) {
            List<Object> copy = new ArrayList<>(collection.size());
            collection.forEach(item -> copy.add(copyValue(item)));
            return copy;
        }
        if (value != null && value.getClass().isArray()) {
            int length = Array.getLength(value);
            List<Object> copy = new ArrayList<>(length);
            for (int index = 0; index < length; index++) {
                copy.add(copyValue(Array.get(value, index)));
            }
            return copy;
        }
        return value;
    }

    /**
     * 规范化资源类型。
     *
     * @param resourceType 原始资源类型
     * @return 大写资源类型
     */
    private static String normalizeResourceType(String resourceType) {
        return resourceType == null ? "" : resourceType.trim().toUpperCase(Locale.ROOT);
    }

    /**
     * 规范化字段名，忽略大小写和分隔符。
     *
     * @param key 原始字段名
     * @return 仅含小写字母和数字的字段名
     */
    private static String normalizeKey(String key) {
        return key == null ? "" : key.replaceAll("[^A-Za-z0-9]", "").toLowerCase(Locale.ROOT);
    }
}
