package tech.easyflow.ai.service.discovery;

import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.stereotype.Component;
import tech.easyflow.ai.entity.ModelProvider;
import tech.easyflow.common.web.exceptions.BusinessException;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 阿里百炼可部署基础模型目录适配器。
 */
@Component
public class AliyunRemoteModelAdapter implements RemoteModelProviderAdapter {

    /** 单页最大模型数量。 */
    private static final int PAGE_SIZE = 100;
    /** 最大安全分页数。 */
    private static final int MAX_PAGES = 10;
    /** 当前未接入的百炼媒体生成及内部算法模型。 */
    private static final Set<String> UNSUPPORTED_MODEL_IDS = Set.of(
            "animate-anyone",
            "animate-anyone-detect",
            "emo",
            "emo-detect",
            "mock-algo-v1",
            "wanx-v1-0521");

    /**
     * 分页获取阿里百炼基础模型 ID。
     *
     * @param provider 已保存的服务商配置
     * @param httpClient 受控 HTTP 客户端
     * @return 原始模型 ID 列表
     * @throws BusinessException 响应结构不兼容时抛出
     */
    @Override
    public List<String> fetchModelIds(ModelProvider provider, RemoteModelHttpClient httpClient) {
        List<String> modelIds = new ArrayList<>();
        for (int page = 1; page <= MAX_PAGES; page++) {
            JsonNode root = httpClient.getJson(provider, "/api/v1/deployments/models",
                    queryParameters(page));
            JsonNode models = modelsNode(root);
            if (models == null || !models.isArray()) {
                throw new BusinessException(502, 50233, "阿里百炼模型列表响应格式不兼容");
            }
            models.forEach(item -> {
                JsonNode value = item.get("model_name");
                if (value != null && value.isTextual()) {
                    String modelId = value.asText();
                    if (!modelId.isBlank() && !UNSUPPORTED_MODEL_IDS.contains(modelId)) {
                        modelIds.add(modelId);
                    }
                }
            });
            if (!hasNextPage(root, models.size(), page)) {
                break;
            }
        }
        return modelIds;
    }

    /**
     * 构建单页受控查询参数。
     *
     * @param page 页码
     * @return 查询参数
     */
    private Map<String, String> queryParameters(int page) {
        Map<String, String> parameters = new LinkedHashMap<>();
        parameters.put("model_source", "base");
        parameters.put("page_no", String.valueOf(page));
        parameters.put("page_size", String.valueOf(PAGE_SIZE));
        // v1.0 才会返回当前完整的可部署基础模型目录；省略时可能退回旧版模型集合。
        parameters.put("version", "v1.0");
        return parameters;
    }

    /**
     * 兼容官方 output 节点及历史根节点、data 节点中的 models 数组。
     *
     * @param root JSON 根节点
     * @return models 节点
     */
    private JsonNode modelsNode(JsonNode root) {
        return responsePayload(root).get("models");
    }

    /**
     * 根据明确分页字段或当前页数量判断是否继续。
     *
     * @param root JSON 根节点
     * @param currentSize 当前页数量
     * @param page 当前页码
     * @return 需要继续分页返回 true
     */
    private boolean hasNextPage(JsonNode root, int currentSize, int page) {
        JsonNode payload = responsePayload(root);
        boolean hasMore = payload.path("has_more").asBoolean(false);
        long total = payload.path("total").asLong(payload.path("total_count").asLong(-1));
        int responsePage = payload.path("page_no").asInt(page);
        int responsePageSize = payload.path("page_size").asInt(PAGE_SIZE);
        if (hasMore) {
            return true;
        }
        if (total >= 0) {
            return (long) responsePage * responsePageSize < total;
        }
        return currentSize == PAGE_SIZE;
    }

    /**
     * 获取承载模型列表与分页字段的响应节点。
     *
     * @param root JSON 根节点
     * @return 官方 output、嵌套 output、data 或根节点
     */
    private JsonNode responsePayload(JsonNode root) {
        JsonNode output = root.path("output");
        if (output.isObject()) {
            return output;
        }
        JsonNode data = root.path("data");
        JsonNode nestedOutput = data.path("output");
        if (nestedOutput.isObject()) {
            return nestedOutput;
        }
        return data.isObject() ? data : root;
    }
}
