package com.gzzm.lobster.api.admin;

import com.gzzm.lobster.common.LobsterException;
import com.gzzm.lobster.common.ModelServiceTier;
import com.gzzm.lobster.config.LobsterConfig;
import com.gzzm.lobster.identity.AdminGuard;
import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.llm.ModelProfile;
import com.gzzm.lobster.llm.ModelProfileDao;
import com.gzzm.lobster.llm.ModelProtocol;
import com.gzzm.lobster.llm.ModelProvider;
import net.cyan.arachne.HttpMethod;
import net.cyan.arachne.annotation.Service;
import net.cyan.nest.annotation.Inject;

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

/**
 * AdminModelApi —— 模型配置后台 CRUD / ModelProfile admin CRUD.
 *
 * <p>列表回显对 {@code apiKey} 做脱敏；详情/编辑接口仅管理员可访问，返回完整 {@code apiKey} 便于查看和修改。
 */
@Service
public class AdminModelApi {

    @Inject private ModelProfileDao modelDao;

    @Service(url = "/ai/api/admin/models/list", method = HttpMethod.all)
    public Map<String, Object> list(Integer offset, Integer limit) throws Exception {
        AdminGuard.requireAdmin();
        int o = offset == null ? 0 : Math.max(0, offset);
        int l = limit == null
                ? LobsterConfig.getListDefaultPageSize()
                : Math.min(LobsterConfig.getListMaxPageSize(), Math.max(1, limit));
        List<ModelProfile> rows = modelDao.listAll(o, l);
        List<Map<String, Object>> items = new ArrayList<>();
        for (ModelProfile p : rows) items.add(toMap(p, false));
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("items", items);
        out.put("total", modelDao.countAll());
        out.put("offset", o);
        out.put("limit", l);
        return out;
    }

    @Service(url = "/ai/api/admin/models", method = HttpMethod.all)
    public Map<String, Object> get(String id) throws Exception {
        AdminGuard.requireAdmin();
        if (id == null || id.isEmpty()) throw new LobsterException("admin.model.invalid", "id required");
        ModelProfile p = modelDao.getProfile(id);
        if (p == null) throw new LobsterException("admin.model.not_found", "Model not found: " + id);
        return toMap(p, true);
    }

    @Service(url = "/ai/api/admin/models/create", method = HttpMethod.post)
    public Map<String, Object> create(String modelId, String displayName, String provider,
                                      String protocol, String endpoint, String apiKey,
                                      Boolean nativeToolCalling, Boolean streaming,
                                      Boolean reasoning, Boolean thinkingEnabled, Boolean multimodal,
                                      Integer contextWindow, Integer maxOutputTokens,
                                      Integer firstTokenTimeoutMs, Integer totalTimeoutMs,
                                      String serviceTier, Boolean enabled, Integer priority) throws Exception {
        UserContext admin = AdminGuard.requireAdmin();
        if (modelId == null || modelId.trim().isEmpty()) {
            throw new LobsterException("admin.model.invalid", "modelId required");
        }
        if (provider == null || protocol == null || endpoint == null) {
            throw new LobsterException("admin.model.invalid", "provider/protocol/endpoint required");
        }
        if (modelDao.getProfile(modelId) != null) {
            throw new LobsterException("admin.model.conflict", "Model already exists: " + modelId);
        }
        ModelProfile p = new ModelProfile();
        p.setModelId(modelId);
        p.setDisplayName(displayName);
        p.setProvider(ModelProvider.fromString(provider));
        p.setProtocol(ModelProtocol.fromString(protocol));
        p.setEndpoint(endpoint);
        p.setApiKey(apiKey);
        p.setNativeToolCalling(nativeToolCalling == null ? Boolean.TRUE : nativeToolCalling);
        p.setStreaming(streaming == null ? Boolean.TRUE : streaming);
        p.setReasoning(Boolean.TRUE.equals(reasoning));
        p.setThinkingEnabled(Boolean.TRUE.equals(thinkingEnabled));
        p.setMultimodal(Boolean.TRUE.equals(multimodal));
        p.setContextWindow(contextWindow);
        p.setMaxOutputTokens(maxOutputTokens);
        p.setFirstTokenTimeoutMs(firstTokenTimeoutMs);
        p.setTotalTimeoutMs(totalTimeoutMs);
        p.setServiceTier(serviceTier == null ? ModelServiceTier.standard : ModelServiceTier.valueOf(serviceTier));
        p.setEnabled(enabled == null ? Boolean.TRUE : enabled);
        p.setPriority(priority == null ? Integer.valueOf(0) : priority);
        p.setOrgId(admin.getOrgId());
        p.setCreateTime(new Date());
        p.setUpdateTime(new Date());
        modelDao.save(p);
        return toMap(p, true);
    }

    @Service(url = "/ai/api/admin/models/update", method = HttpMethod.post)
    public Map<String, Object> update(String id, String displayName, String provider,
                                      String protocol, String endpoint, String apiKey,
                                      Boolean nativeToolCalling, Boolean streaming,
                                      Boolean reasoning, Boolean thinkingEnabled, Boolean multimodal,
                                      Integer contextWindow, Integer maxOutputTokens,
                                      Integer firstTokenTimeoutMs, Integer totalTimeoutMs,
                                      String serviceTier, Boolean enabled, Integer priority) throws Exception {
        AdminGuard.requireAdmin();
        if (id == null || id.isEmpty()) throw new LobsterException("admin.model.invalid", "id required");
        ModelProfile p = modelDao.getProfile(id);
        if (p == null) throw new LobsterException("admin.model.not_found", "Model not found: " + id);
        if (displayName != null) p.setDisplayName(displayName);
        if (provider != null) p.setProvider(ModelProvider.fromString(provider));
        if (protocol != null) p.setProtocol(ModelProtocol.fromString(protocol));
        if (endpoint != null) p.setEndpoint(endpoint);
        // 仅当 apiKey 传入非空且非脱敏回显值时才覆盖，避免误清空
        if (apiKey != null && !apiKey.isEmpty() && !apiKey.contains("*")) p.setApiKey(apiKey);
        if (nativeToolCalling != null) p.setNativeToolCalling(nativeToolCalling);
        if (streaming != null) p.setStreaming(streaming);
        if (reasoning != null) p.setReasoning(reasoning);
        if (thinkingEnabled != null) p.setThinkingEnabled(thinkingEnabled);
        if (multimodal != null) p.setMultimodal(multimodal);
        if (contextWindow != null) p.setContextWindow(contextWindow);
        if (maxOutputTokens != null) p.setMaxOutputTokens(maxOutputTokens);
        if (firstTokenTimeoutMs != null) p.setFirstTokenTimeoutMs(firstTokenTimeoutMs);
        if (totalTimeoutMs != null) p.setTotalTimeoutMs(totalTimeoutMs);
        if (serviceTier != null) p.setServiceTier(ModelServiceTier.valueOf(serviceTier));
        if (enabled != null) p.setEnabled(enabled);
        if (priority != null) p.setPriority(priority);
        p.setUpdateTime(new Date());
        modelDao.save(p);
        return toMap(p, true);
    }

    @Service(url = "/ai/api/admin/models/delete", method = HttpMethod.post)
    public Map<String, Object> delete(String id) throws Exception {
        AdminGuard.requireAdmin();
        if (id == null || id.isEmpty()) throw new LobsterException("admin.model.invalid", "id required");
        int n = modelDao.deleteById(id);
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("deleted", n);
        return out;
    }

    @Service(url = "/ai/api/admin/models/enabled", method = HttpMethod.post)
    public Map<String, Object> toggleEnabled(String id, Boolean enabled) throws Exception {
        AdminGuard.requireAdmin();
        if (id == null || id.isEmpty()) throw new LobsterException("admin.model.invalid", "id required");
        ModelProfile p = modelDao.getProfile(id);
        if (p == null) throw new LobsterException("admin.model.not_found", "Model not found: " + id);
        p.setEnabled(enabled == null ? !Boolean.TRUE.equals(p.getEnabled()) : enabled);
        p.setUpdateTime(new Date());
        modelDao.save(p);
        return toMap(p, false);
    }

    private static Map<String, Object> toMap(ModelProfile p, boolean includeApiKey) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("modelId", p.getModelId());
        m.put("displayName", p.getDisplayName());
        m.put("provider", p.getProvider());
        m.put("protocol", p.getProtocol());
        m.put("endpoint", p.getEndpoint());
        if (includeApiKey) {
            m.put("apiKey", p.getApiKey());
        }
        m.put("apiKeyMasked", maskApiKey(p.getApiKey()));
        m.put("nativeToolCalling", p.getNativeToolCalling());
        m.put("streaming", p.getStreaming());
        m.put("reasoning", p.getReasoning());
        m.put("thinkingEnabled", p.getThinkingEnabled());
        m.put("multimodal", p.getMultimodal());
        m.put("contextWindow", p.getContextWindow());
        m.put("maxOutputTokens", p.getMaxOutputTokens());
        m.put("firstTokenTimeoutMs", p.getFirstTokenTimeoutMs());
        m.put("totalTimeoutMs", p.getTotalTimeoutMs());
        m.put("serviceTier", p.getServiceTier());
        m.put("enabled", p.getEnabled());
        m.put("priority", p.getPriority());
        m.put("orgId", p.getOrgId());
        m.put("createTime", p.getCreateTime());
        m.put("updateTime", p.getUpdateTime());
        return m;
    }

    private static String maskApiKey(String raw) {
        if (raw == null || raw.isEmpty()) return null;
        int tail = Math.max(0, LobsterConfig.getApiKeyMaskTailLen());
        if (raw.length() <= tail) return repeat('*', raw.length());
        return repeat('*', raw.length() - tail) + raw.substring(raw.length() - tail);
    }

    private static String repeat(char c, int n) {
        StringBuilder sb = new StringBuilder(n);
        for (int i = 0; i < n; i++) sb.append(c);
        return sb.toString();
    }
}
