package com.gzzm.lobster.api.admin;

import com.gzzm.lobster.common.IdGenerator;
import com.gzzm.lobster.common.LobsterException;
import com.gzzm.lobster.common.SkillRuntimeKind;
import com.gzzm.lobster.common.SkillScope;
import com.gzzm.lobster.config.LobsterConfig;
import com.gzzm.lobster.identity.AdminGuard;
import com.gzzm.lobster.identity.UserContext;
import com.gzzm.lobster.skill.SkillDefinition;
import com.gzzm.lobster.skill.SkillDefinitionDao;
import com.gzzm.lobster.skill.SkillInvocationDao;
import com.gzzm.lobster.skill.SkillInvocationLog;
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;

/**
 * AdminSkillApi —— Skill 后台 CRUD / Skill admin CRUD.
 */
@Service
public class AdminSkillApi {

    @Inject private SkillDefinitionDao skillDao;
    @Inject private SkillInvocationDao invocationDao;

    @Service(url = "/ai/api/admin/skills/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<SkillDefinition> rows = skillDao.listAll(o, l);
        List<Map<String, Object>> items = new ArrayList<>();
        for (SkillDefinition s : rows) items.add(toSummary(s));
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("items", items);
        out.put("total", skillDao.countAll());
        out.put("offset", o);
        out.put("limit", l);
        return out;
    }

    @Service(url = "/ai/api/admin/skills/{$0}", method = HttpMethod.all)
    public Map<String, Object> get(String skillId) throws Exception {
        AdminGuard.requireAdmin();
        SkillDefinition s = skillDao.getSkill(skillId);
        if (s == null) throw new LobsterException("admin.skill.not_found", "Skill not found: " + skillId);
        return toFull(s);
    }

    @Service(url = "/ai/api/admin/skills/create", method = HttpMethod.post)
    public Map<String, Object> create(String name, String scope, String triggerCondition,
                                      String guidance, String runtimeKind,
                                      String intermediateStatesJson, String queryScopesJson,
                                      String resumeChecksJson, String completionRequirementsJson,
                                      Boolean enabled) throws Exception {
        UserContext admin = AdminGuard.requireAdmin();
        if (name == null || name.trim().isEmpty()) {
            throw new LobsterException("admin.skill.invalid", "name required");
        }
        SkillDefinition s = new SkillDefinition();
        s.setSkillId(IdGenerator.prefixed("sk_"));
        s.setName(name);
        s.setScope(scope == null ? SkillScope.system : SkillScope.valueOf(scope));
        s.setOrgId(admin.getOrgId());
        s.setTriggerCondition(triggerCondition);
        s.setGuidance(guidance);
        s.setRuntimeKind(runtimeKind == null ? SkillRuntimeKind.single_shot : SkillRuntimeKind.valueOf(runtimeKind));
        s.setIntermediateStatesJson(intermediateStatesJson);
        s.setQueryScopesJson(queryScopesJson);
        s.setResumeChecksJson(resumeChecksJson);
        s.setCompletionRequirementsJson(completionRequirementsJson);
        s.setVersion(1);
        s.setEnabled(enabled == null ? Boolean.TRUE : enabled);
        s.setCreateTime(new Date());
        s.setUpdateTime(new Date());
        skillDao.save(s);
        Map<String, Object> out = toFull(s);
        List<String> warnings = descriptionWarnings(triggerCondition);
        if (!warnings.isEmpty()) out.put("warnings", warnings);
        return out;
    }

    @Service(url = "/ai/api/admin/skills/{$0}/update", method = HttpMethod.post)
    public Map<String, Object> update(String skillId, String name, String scope, String triggerCondition,
                                      String guidance, String runtimeKind,
                                      String intermediateStatesJson, String queryScopesJson,
                                      String resumeChecksJson, String completionRequirementsJson,
                                      Boolean enabled) throws Exception {
        AdminGuard.requireAdmin();
        SkillDefinition s = skillDao.getSkill(skillId);
        if (s == null) throw new LobsterException("admin.skill.not_found", "Skill not found: " + skillId);
        if (name != null) s.setName(name);
        if (scope != null) s.setScope(SkillScope.valueOf(scope));
        if (triggerCondition != null) s.setTriggerCondition(triggerCondition);
        if (guidance != null) s.setGuidance(guidance);
        if (runtimeKind != null) s.setRuntimeKind(SkillRuntimeKind.valueOf(runtimeKind));
        if (intermediateStatesJson != null) s.setIntermediateStatesJson(intermediateStatesJson);
        if (queryScopesJson != null) s.setQueryScopesJson(queryScopesJson);
        if (resumeChecksJson != null) s.setResumeChecksJson(resumeChecksJson);
        if (completionRequirementsJson != null) s.setCompletionRequirementsJson(completionRequirementsJson);
        if (enabled != null) s.setEnabled(enabled);
        s.setVersion((s.getVersion() == null ? 0 : s.getVersion()) + 1);
        s.setUpdateTime(new Date());
        skillDao.save(s);
        Map<String, Object> out = toFull(s);
        List<String> warnings = descriptionWarnings(triggerCondition != null ? triggerCondition : s.getTriggerCondition());
        if (!warnings.isEmpty()) out.put("warnings", warnings);
        return out;
    }

    /**
     * description（triggerCondition）软校验——对齐 Claude Code"前置关键词 + 场景具体"的写法。
     *
     * <p>不阻塞保存，只返回 warnings 供前端提示作者改。检查项：
     * <ul>
     *   <li>非空：无 description 模型根本不知道何时召回</li>
     *   <li>长度 ≥ 40 字符：太短一般说不清场景</li>
     *   <li>命中触发关键词之一：Use this skill / Use when / when / 触发 / 使用 / 用于</li>
     *   <li>长度 ≥ 1800 字符：提醒接近 2000 字段上限，后续扩展空间有限</li>
     * </ul>
     */
    private static List<String> descriptionWarnings(String desc) {
        List<String> out = new ArrayList<>();
        if (desc == null || desc.trim().isEmpty()) {
            out.add("description 为空：模型无法判断何时该用这个 skill，调用率会为零");
            return out;
        }
        String trimmed = desc.trim();
        if (trimmed.length() < 40) {
            out.add("description 仅 " + trimmed.length() + " 字符，建议 ≥ 40：写清楚场景 + 关键词 + 例外情况，避免模型漏召回");
        }
        // 只承认"显式触发意图"的组合短语——避免裸 when 把"format X when Y"
        // 这类描述误判成合规（命中率爆炸会让软校验失去提醒意义）
        String lower = trimmed.toLowerCase();
        boolean hasTrigger = lower.contains("use this skill")
                || lower.contains("use this when")
                || lower.contains("use when")
                || lower.contains("trigger when")
                || lower.contains("triggers include")
                || trimmed.contains("触发条件")
                || trimmed.contains("用于")
                || trimmed.contains("用来");
        if (!hasTrigger) {
            out.add("description 缺少触发提示词（如 'Use this skill when ...' / '用于 ...' / '触发 ...'），"
                    + "模型按 description 判断召回时可能打不准");
        }
        if (trimmed.length() >= 1800) {
            out.add("description 长度 " + trimmed.length() + " 接近字段上限 2000，建议压缩关键内容前置，"
                    + "次要场景迁移到 guidance 正文");
        }
        return out;
    }

    @Service(url = "/ai/api/admin/skills/{$0}/delete", method = HttpMethod.post)
    public Map<String, Object> delete(String skillId) throws Exception {
        AdminGuard.requireAdmin();
        int n = skillDao.deleteById(skillId);
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("deleted", n);
        return out;
    }

    /**
     * Skill 调用统计——观察召回率与 SKILL.md 可读性. GET 也可（method=all）。
     *
     * <p>返回：{@code total}（总调用数）、{@code duplicates}（幂等分支命中数——高
     * 说明模型反复读同一 skill，SKILL.md 结构可能不够清晰）、{@code recent} 列表（最近
     * 20 条，用于 sanity check）。
     */
    @Service(url = "/ai/api/admin/skills/{$0}/invocations", method = HttpMethod.all)
    public Map<String, Object> invocationStats(String skillId) throws Exception {
        AdminGuard.requireAdmin();
        long total = invocationDao.countBySkill(skillId);
        long duplicates = invocationDao.countDuplicateBySkill(skillId, Boolean.TRUE);
        List<SkillInvocationLog> recent = invocationDao.listBySkill(skillId, 0, 20);
        List<Map<String, Object>> recentRows = new ArrayList<>();
        for (SkillInvocationLog i : recent) {
            Map<String, Object> row = new LinkedHashMap<>();
            row.put("threadId", i.getThreadId());
            row.put("userId", i.getUserId());
            row.put("runId", i.getRunId());
            row.put("duplicate", i.getDuplicate());
            row.put("invokedAt", i.getInvokedAt());
            recentRows.add(row);
        }
        Map<String, Object> out = new LinkedHashMap<>();
        out.put("skillId", skillId);
        out.put("total", total);
        out.put("duplicates", duplicates);
        // 冗余比率：duplicate / total —— 高比率意味着 SKILL.md 结构需要优化
        out.put("duplicateRatio", total == 0 ? 0d : (double) duplicates / total);
        out.put("recent", recentRows);
        return out;
    }

    @Service(url = "/ai/api/admin/skills/{$0}/enabled", method = HttpMethod.post)
    public Map<String, Object> toggleEnabled(String skillId, Boolean enabled) throws Exception {
        AdminGuard.requireAdmin();
        SkillDefinition s = skillDao.getSkill(skillId);
        if (s == null) throw new LobsterException("admin.skill.not_found", "Skill not found: " + skillId);
        s.setEnabled(enabled == null ? !Boolean.TRUE.equals(s.getEnabled()) : enabled);
        s.setUpdateTime(new Date());
        skillDao.save(s);
        return toFull(s);
    }

    private static Map<String, Object> toSummary(SkillDefinition s) {
        Map<String, Object> m = new LinkedHashMap<>();
        m.put("skillId", s.getSkillId());
        m.put("name", s.getName());
        m.put("scope", s.getScope());
        m.put("triggerCondition", s.getTriggerCondition());
        m.put("runtimeKind", s.getRuntimeKind());
        m.put("version", s.getVersion());
        m.put("enabled", s.getEnabled());
        m.put("updateTime", s.getUpdateTime());
        return m;
    }

    private static Map<String, Object> toFull(SkillDefinition s) {
        Map<String, Object> m = toSummary(s);
        m.put("guidance", s.getGuidance());
        m.put("intermediateStatesJson", s.getIntermediateStatesJson());
        m.put("queryScopesJson", s.getQueryScopesJson());
        m.put("resumeChecksJson", s.getResumeChecksJson());
        m.put("completionRequirementsJson", s.getCompletionRequirementsJson());
        m.put("orgId", s.getOrgId());
        m.put("assetBundleRef", s.getAssetBundleRef());
        m.put("pythonPackagesJson", s.getPythonPackagesJson());
        m.put("createTime", s.getCreateTime());
        return m;
    }
}
