package com.ruoyi.jarvis.service.impl;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.jarvis.service.ISocialMediaService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

/**
 * 小红书/抖音内容生成Service业务层处理
 *
 * @author ruoyi
 * @date 2025-01-XX
 */
@Service
public class SocialMediaServiceImpl implements ISocialMediaService
{
    private static final Logger log = LoggerFactory.getLogger(SocialMediaServiceImpl.class);

    @Autowired(required = false)
    private StringRedisTemplate redisTemplate;

    @Value("${jarvis.server.jarvis-java.base-url:http://127.0.0.1:6666}")
    private String jarvisBaseUrl;

    // Redis Key 前缀
    private static final String REDIS_KEY_PREFIX = "social_media:prompt:";

    // 模板键名列表
    private static final String[] TEMPLATE_KEYS = {
        "keywords",
        "content:xhs",
        "content:douyin",
        "content:both",
        "xianyu:wenan_base",
        "xianyu:jiaonixiadan_extra",
        "xianyu:title_clean_regex",
        "xianyu:seed_note_prompt"
    };

    // 模板说明
    private static final Map<String, String> TEMPLATE_DESCRIPTIONS = new HashMap<String, String>() {{
        put("keywords", "关键词提取提示词模板\n占位符：%s - 商品名称");
        put("content:xhs", "小红书文案生成提示词模板\n占位符：%s - 商品名称，%s - 价格信息，%s - 关键词信息");
        put("content:douyin", "抖音文案生成提示词模板\n占位符：%s - 商品名称，%s - 价格信息，%s - 关键词信息");
        put("content:both", "通用文案生成提示词模板\n占位符：%s - 商品名称，%s - 价格信息，%s - 关键词信息");
        put("xianyu:wenan_base", "闲鱼文案·正文基础说明\n用于「一键代下」与「教你下单」两版文案中，紧接在标题/型号行之后（纯文本，无占位符）");
        put("xianyu:jiaonixiadan_extra", "闲鱼文案·教你下单版尾部附加说明\n接在「更新日期：yyyy-MM-dd」之后（纯文本）");
        put("xianyu:title_clean_regex", "闲鱼文案·标题/型号清洗正则\n从标题与型号备注中移除营销敏感片段；须为 Java 正则，匹配到的内容会被删除");
        put("xianyu:seed_note_prompt", "闲鱼文案·种草文案提示词模板\n占位符：{{goods_title}} {{goods_model}} {{goods_type}} {{goods_brand}} {{channel_source}} {{official_price}} {{sell_price}} {{warranty}} {{delivery_install}}");
    }};

    /** 多套大模型 JSON 存储，与 Jarvis_java SocialMediaLlmClient 一致 */
    private static final String LLM_PROFILES_STORE_KEY = "social_media:llm:profiles_store";
    /** 旧版单套 Redis 键，首次读取时迁移进 profiles_store 后删除 */
    private static final String LLM_KEY_MODE = "social_media:llm:mode";
    private static final String LLM_KEY_BASE_URL = "social_media:llm:base_url";
    private static final String LLM_KEY_API_KEY = "social_media:llm:api_key";
    private static final String LLM_KEY_MODEL = "social_media:llm:model";

    private static final String LLM_MODE_OLLAMA = "ollama";
    private static final String LLM_MODE_OPENAI = "openai";

    /** 闲鱼文案-通用基础说明（Redis 未配置时使用） */
    private static final String DEFAULT_XIANYU_WENAN_BASE =
            "全新未拆封正品，包邮包安装，支持查验后再签收。\n"
            + "运损可免费换新。\n"
            + "售后全部支持全国联保。";
    /** 闲鱼文案-教你下单附加说明（不含更新日期） */
    private static final String DEFAULT_XIANYU_JIAONIXIADAN_EXTRA =
            "\n无偿提供下单方案，包价格成立：\n"
            + "只要告诉我【需要下单的型号 + 收货地址】，\n"
            + "我会根据你所在地区和需求，\n"
            + "优先回复你合适的下单渠道和详细步骤，让你安全省钱地完成下单。";
    /** 标题/型号清洗：去掉营销敏感词 */
    private static final String DEFAULT_XIANYU_TITLE_CLEAN_REGEX = "以旧|政府|换新|领取|国家|补贴|15%|20%|国补|立减|【|】";
    /** 闲鱼种草文案提示词模板（Redis 未配置时使用） */
    private static final String DEFAULT_XIANYU_SEED_NOTE_PROMPT =
            "# 角色设定\n"
            + "你是拥有10年家电行业经验的资深渠道商，同时是闲鱼平台家电类目TOP级种草文案达人，精通闲鱼家电用户的消费心理、搜索流量规则、高转化文案写作逻辑，擅长用专业且亲切的口吻，为全新未拆封的正品家电，写出强信任、高种草、高成交的闲鱼转卖文案，拒绝冰冷的官方参数堆砌、浮夸的营销硬广、二手闲置话术。\n\n"
            + "# 核心任务\n"
            + "严格基于我提供的家电商品信息，生成一段完全适配闲鱼平台、符合全新正品家电属性、有强吸引力、高转化的种草文案，全程遵循以下所有规则。\n\n"
            + "# 家电商品输入信息（接口传入）\n"
            + "【必传核心信息】\n"
            + "1. 商品完整标题：{{goods_title}}\n"
            + "2. 商品精准完整型号：{{goods_model}}\n"
            + "3. 家电品类类型：{{goods_type}}\n\n"
            + "【可选补充信息（无则按默认兜底规则生成）】\n"
            + "4. 商品品牌：{{goods_brand}}\n"
            + "5. 渠道来源：{{channel_source}}\n"
            + "6. 官方指导价：{{official_price}}\n"
            + "7. 闲鱼售卖价格：{{sell_price}}\n"
            + "8. 官方质保政策：{{warranty}}\n"
            + "9. 配送安装政策：{{delivery_install}}\n\n"
            + "# 字段优先级&兜底规则（必须严格遵守）\n"
            + "1. 型号优先级：必须以「{{goods_model}}」为唯一精准型号，商品标题里的型号简写仅做补充，禁止写错、简写型号\n"
            + "2. 品类优先级：必须以「{{goods_type}}」为唯一品类标准，所有场景、卖点、话术必须围绕该家电品类展开\n"
            + "3. 品牌兜底：若{{goods_brand}}为空，自动从{{goods_title}}中提取品牌名，提取不到则不强制体现\n"
            + "4. 渠道兜底：若{{channel_source}}为空，默认使用「品牌授权经销商直供」，禁止使用二手、个人闲置相关渠道描述\n"
            + "5. 价格兜底：若{{official_price}}为空，默认使用「比官方旗舰店优惠力度大」相关话术；若{{sell_price}}为空，不写固定售价，引导用户私信询价\n"
            + "6. 售后兜底：若{{warranty}}为空，默认使用「品牌官方全国联保，和官方旗舰店享受同等售后权益」\n"
            + "7. 配送兜底：若{{delivery_install}}为空，默认使用「全国包邮送货入户，乡镇可达」\n\n"
            + "# 核心字段强制融合规则（必须100%严格执行，解决生硬堆砌问题）\n"
            + "1. 标题植入规则：生成的闲鱼标题，必须同时包含「{{goods_type}}+{{goods_model}}+品牌+全新正品」4个核心元素，同时适配闲鱼家电用户搜索习惯，字数控制在25-35字\n"
            + "2. 正文植入规则：\n"
            + " - 开篇前30字，必须同时出现「{{goods_type}}+{{goods_model}}」，强化型号和品类，同时给出正品信任背书\n"
            + " - 正文卖点模块，必须围绕「{{goods_type}}」的品类核心需求展开，同时绑定{{goods_model}}型号，禁止脱离品类写通用卖点\n"
            + " - 正文至少3次自然提及{{goods_model}}完整型号，适配闲鱼搜索权重规则，禁止只出现1次\n"
            + " - 正文所有场景化描述，必须贴合{{goods_type}}的使用场景，比如空调对应卧室/客厅制冷、冰箱对应食材囤货/嵌入装修、洗衣机对应家庭大容量/除菌等\n"
            + "3. 结尾植入规则：结尾行动引导和话题标签，必须包含「{{goods_type}}」相关流量词，标签数量4-6个，必须有1个标签带{{goods_model}}型号\n"
            + "4. 卖点提取规则：核心卖点自动从{{goods_title}}中提取，优先提取能效、容量、变频、智能、超薄、除菌等家电核心决策卖点，转化为场景化话术，禁止参数堆砌\n\n"
            + "# 创作必须严格遵守的通用规则\n"
            + "1. 全程使用第一人称「我/我们」写作，贴合家电渠道商的专业且亲切的身份，像给朋友推荐靠谱好货，绝对不能出现官方旗舰店硬广话术、二手个人闲置转手话术，全程围绕「全新未拆封正品家电」核心属性创作\n"
            + "2. 文案100%基于接口传入的信息创作，不得凭空捏造与输入信息不符的功能、参数、质保、服务政策，核心卖点必须转化为用户可感知的场景化好处\n"
            + "3. 必须完整包含以下核心模块，顺序不可调换，缺一不可：吸睛搜索标题、开篇信任背书、正品保障、卖点拆解、价格优势、配送安装、售后承诺、结尾行动和标签\n"
            + "4. 文案排版：短句为主，段落清晰，每段不超过3行，手机阅读友好\n"
            + "5. 字数要求：正文控制在400-600字\n"
            + "6. 合规要求：严格符合闲鱼平台规则和3C家电宣传规范，不使用极限词，不夸大宣传\n\n"
            + "# 输出格式\n"
            + "直接输出最终完整文案，不要额外解释。";

    /**
     * Redis 无记录时与 Jarvis_java SocialMediaService 使用的默认模板一致，供接口回显到前端参考。
     */
    private static final Map<String, String> DEFAULT_PROMPT_TEMPLATES = new HashMap<String, String>() {{
        put("keywords",
                "请从以下商品标题中提取3-5个最核心的关键词，这些关键词要能突出商品的核心卖点和特色。\n"
                        + "要求：\n"
                        + "1. 每个关键词2-4个字\n"
                        + "2. 关键词要能吸引小红书/抖音用户\n"
                        + "3. 用逗号分隔，只返回关键词，不要其他内容\n"
                        + "商品标题：%s");
        put("content:xhs",
                "请为小红书平台生成一篇商品推广文案，要求：\n"
                        + "1. 风格：真实、种草、有温度\n"
                        + "2. 开头：用emoji或感叹句吸引注意\n"
                        + "3. 内容：突出商品亮点、使用场景、性价比\n"
                        + "4. 结尾：引导行动（如：快冲、闭眼入等）\n"
                        + "5. 长度：150-300字\n"
                        + "6. 适当使用emoji和换行\n"
                        + "\n商品信息：\n"
                        + "商品名称：%s\n"
                        + "%s"
                        + "%s"
                        + "\n请直接生成文案内容，不要添加其他说明：");
        put("content:douyin",
                "请为抖音平台生成一篇商品推广文案，要求：\n"
                        + "1. 风格：直接、有冲击力、吸引眼球\n"
                        + "2. 开头：用疑问句或对比句抓住注意力\n"
                        + "3. 内容：强调价格优势、限时优惠、稀缺性\n"
                        + "4. 结尾：制造紧迫感，引导立即行动\n"
                        + "5. 长度：100-200字\n"
                        + "6. 使用短句，节奏感强\n"
                        + "\n商品信息：\n"
                        + "商品名称：%s\n"
                        + "%s"
                        + "%s"
                        + "\n请直接生成文案内容，不要添加其他说明：");
        put("content:both",
                "请生成一篇适合小红书和抖音平台的商品推广文案，要求：\n"
                        + "1. 风格：真实、有吸引力\n"
                        + "2. 突出商品亮点和价格优势\n"
                        + "3. 长度：150-250字\n"
                        + "\n商品信息：\n"
                        + "商品名称：%s\n"
                        + "%s"
                        + "%s"
                        + "\n请直接生成文案内容，不要添加其他说明：");
        put("xianyu:wenan_base", DEFAULT_XIANYU_WENAN_BASE);
        put("xianyu:jiaonixiadan_extra", DEFAULT_XIANYU_JIAONIXIADAN_EXTRA);
        put("xianyu:title_clean_regex", DEFAULT_XIANYU_TITLE_CLEAN_REGEX);
        put("xianyu:seed_note_prompt", DEFAULT_XIANYU_SEED_NOTE_PROMPT);
    }};

    /**
     * 提取商品标题关键词
     */
    @Override
    public Map<String, Object> extractKeywords(String productName) {
        Map<String, Object> result = new HashMap<>();

        if (StringUtils.isEmpty(productName)) {
            result.put("success", false);
            result.put("error", "商品名称不能为空");
            return result;
        }

        try {
            // 调用 jarvis_java 的接口
            String url = jarvisBaseUrl + "/jarvis/social-media/extract-keywords";
            JSONObject requestBody = new JSONObject();
            requestBody.put("productName", productName);

            log.info("调用jarvis_java提取关键词接口，URL: {}, 参数: {}", url, requestBody.toJSONString());
            String response = HttpUtils.sendJsonPost(url, requestBody.toJSONString());
            log.info("jarvis_java响应: {}", response);

            if (StringUtils.isEmpty(response)) {
                throw new Exception("jarvis_java返回空结果");
            }

            // 解析响应
            Object parsed = JSON.parse(response);
            if (parsed instanceof JSONObject) {
                JSONObject jsonResponse = (JSONObject) parsed;
                if (jsonResponse.getInteger("code") == 200) {
                    Object data = jsonResponse.get("data");
                    if (data instanceof Map) {
                        @SuppressWarnings("unchecked")
                        Map<String, Object> dataMap = (Map<String, Object>) data;
                        return dataMap;
                    }
                } else {
                    String msg = jsonResponse.getString("msg");
                    result.put("success", false);
                    result.put("error", msg != null ? msg : "提取关键词失败");
                    return result;
                }
            }

            result.put("success", false);
            result.put("error", "响应格式错误");
            return result;

        } catch (Exception e) {
            log.error("提取关键词失败", e);
            result.put("success", false);
            result.put("error", "提取关键词失败: " + e.getMessage());
            return result;
        }
    }

    /**
     * 生成文案
     */
    @Override
    public Map<String, Object> generateContent(String productName, Object originalPrice,
                                                Object finalPrice, String keywords, String style) {
        Map<String, Object> result = new HashMap<>();

        if (StringUtils.isEmpty(productName)) {
            result.put("success", false);
            result.put("error", "商品名称不能为空");
            return result;
        }

        try {
            // 调用 jarvis_java 的接口
            String url = jarvisBaseUrl + "/jarvis/social-media/generate-content";
            JSONObject requestBody = new JSONObject();
            requestBody.put("productName", productName);
            if (originalPrice != null) {
                requestBody.put("originalPrice", parseDouble(originalPrice));
            }
            if (finalPrice != null) {
                requestBody.put("finalPrice", parseDouble(finalPrice));
            }
            if (StringUtils.isNotEmpty(keywords)) {
                requestBody.put("keywords", keywords);
            }
            if (StringUtils.isNotEmpty(style)) {
                requestBody.put("style", style);
            }

            log.info("调用jarvis_java生成文案接口，URL: {}, 参数: {}", url, requestBody.toJSONString());
            String response = HttpUtils.sendJsonPost(url, requestBody.toJSONString());
            log.info("jarvis_java响应: {}", response);

            if (StringUtils.isEmpty(response)) {
                throw new Exception("jarvis_java返回空结果");
            }

            // 解析响应
            Object parsed = JSON.parse(response);
            if (parsed instanceof JSONObject) {
                JSONObject jsonResponse = (JSONObject) parsed;
                if (jsonResponse.getInteger("code") == 200) {
                    Object data = jsonResponse.get("data");
                    if (data instanceof Map) {
                        @SuppressWarnings("unchecked")
                        Map<String, Object> dataMap = (Map<String, Object>) data;
                        return dataMap;
                    }
                } else {
                    String msg = jsonResponse.getString("msg");
                    result.put("success", false);
                    result.put("error", msg != null ? msg : "生成文案失败");
                    return result;
                }
            }

            result.put("success", false);
            result.put("error", "响应格式错误");
            return result;

        } catch (Exception e) {
            log.error("生成文案失败", e);
            result.put("success", false);
            result.put("error", "生成文案失败: " + e.getMessage());
            return result;
        }
    }

    /**
     * 一键生成完整内容（关键词 + 文案 + 图片）
     */
    @Override
    public Map<String, Object> generateCompleteContent(String productImageUrl, String productName,
                                                        Object originalPrice, Object finalPrice, String style) {
        Map<String, Object> result = new HashMap<>();

        if (StringUtils.isEmpty(productName)) {
            result.put("success", false);
            result.put("error", "商品名称不能为空");
            return result;
        }

        try {
            // 调用 jarvis_java 的接口
            String url = jarvisBaseUrl + "/jarvis/social-media/generate-complete";
            JSONObject requestBody = new JSONObject();
            if (StringUtils.isNotEmpty(productImageUrl)) {
                requestBody.put("productImageUrl", productImageUrl);
            }
            requestBody.put("productName", productName);
            if (originalPrice != null) {
                requestBody.put("originalPrice", parseDouble(originalPrice));
            }
            if (finalPrice != null) {
                requestBody.put("finalPrice", parseDouble(finalPrice));
            }
            if (StringUtils.isNotEmpty(style)) {
                requestBody.put("style", style);
            }

            log.info("调用jarvis_java生成完整内容接口，URL: {}, 参数: {}", url, requestBody.toJSONString());
            String response = HttpUtils.sendJsonPost(url, requestBody.toJSONString());
            log.info("jarvis_java响应: {}", response);

            if (StringUtils.isEmpty(response)) {
                throw new Exception("jarvis_java返回空结果");
            }

            // 解析响应
            Object parsed = JSON.parse(response);
            if (parsed instanceof JSONObject) {
                JSONObject jsonResponse = (JSONObject) parsed;
                if (jsonResponse.getInteger("code") == 200) {
                    Object data = jsonResponse.get("data");
                    if (data instanceof Map) {
                        @SuppressWarnings("unchecked")
                        Map<String, Object> dataMap = (Map<String, Object>) data;
                        return dataMap;
                    }
                } else {
                    String msg = jsonResponse.getString("msg");
                    result.put("success", false);
                    result.put("error", msg != null ? msg : "生成完整内容失败");
                    return result;
                }
            }

            result.put("success", false);
            result.put("error", "响应格式错误");
            return result;

        } catch (Exception e) {
            log.error("生成完整内容失败", e);
            result.put("success", false);
            result.put("error", "生成完整内容失败: " + e.getMessage());
            return result;
        }
    }

    /**
     * 获取提示词模板列表
     */
    @Override
    public AjaxResult listPromptTemplates() {
        try {
            Map<String, Object> templates = new HashMap<>();

            for (String key : TEMPLATE_KEYS) {
                Map<String, Object> templateInfo = new HashMap<>();
                templateInfo.put("key", key);
                templateInfo.put("description", TEMPLATE_DESCRIPTIONS.get(key));

                String stored = getTemplateFromRedis(key);
                boolean isDefault = StringUtils.isEmpty(stored);
                templateInfo.put("template", isDefault ? DEFAULT_PROMPT_TEMPLATES.getOrDefault(key, "") : stored);
                templateInfo.put("isDefault", isDefault);

                templates.put(key, templateInfo);
            }

            return AjaxResult.success(templates);
        } catch (Exception e) {
            log.error("获取提示词模板列表失败", e);
            return AjaxResult.error("获取失败: " + e.getMessage());
        }
    }

    /**
     * 获取单个提示词模板
     */
    @Override
    public AjaxResult getPromptTemplate(String key) {
        try {
            if (!isValidKey(key)) {
                return AjaxResult.error("无效的模板键名");
            }

            String stored = getTemplateFromRedis(key);
            boolean isDefault = StringUtils.isEmpty(stored);
            Map<String, Object> data = new HashMap<>();
            data.put("key", key);
            data.put("description", TEMPLATE_DESCRIPTIONS.get(key));
            data.put("template", isDefault ? DEFAULT_PROMPT_TEMPLATES.getOrDefault(key, "") : stored);
            data.put("isDefault", isDefault);

            return AjaxResult.success(data);
        } catch (Exception e) {
            log.error("获取提示词模板失败", e);
            return AjaxResult.error("获取失败: " + e.getMessage());
        }
    }

    /**
     * 保存提示词模板
     */
    @Override
    public AjaxResult savePromptTemplate(Map<String, Object> request) {
        try {
            String key = (String) request.get("key");
            String template = (String) request.get("template");

            if (!isValidKey(key)) {
                return AjaxResult.error("无效的模板键名");
            }

            if (StringUtils.isEmpty(template)) {
                return AjaxResult.error("模板内容不能为空");
            }

            if (redisTemplate == null) {
                return AjaxResult.error("Redis未配置，无法保存模板");
            }

            String redisKey = REDIS_KEY_PREFIX + key;
            String templateValue = template.trim();
            if (StringUtils.isEmpty(templateValue)) {
                return AjaxResult.error("模板内容不能为空");
            }
            if ("xianyu:title_clean_regex".equals(key)) {
                try {
                    Pattern.compile(templateValue);
                } catch (PatternSyntaxException e) {
                    return AjaxResult.error("正则表达式无效: " + e.getMessage());
                }
            }
            redisTemplate.opsForValue().set(redisKey, templateValue);

            log.info("保存提示词模板成功: {}", key);
            return AjaxResult.success("保存成功");

        } catch (Exception e) {
            log.error("保存提示词模板失败", e);
            return AjaxResult.error("保存失败: " + e.getMessage());
        }
    }

    /**
     * 删除提示词模板（恢复默认）
     */
    @Override
    public AjaxResult deletePromptTemplate(String key) {
        try {
            if (!isValidKey(key)) {
                return AjaxResult.error("无效的模板键名");
            }

            if (redisTemplate == null) {
                return AjaxResult.error("Redis未配置，无法删除模板");
            }

            String redisKey = REDIS_KEY_PREFIX + key;
            redisTemplate.delete(redisKey);

            log.info("删除提示词模板成功: {}", key);
            return AjaxResult.success("删除成功，已恢复默认模板");

        } catch (Exception e) {
            log.error("删除提示词模板失败", e);
            return AjaxResult.error("删除失败: " + e.getMessage());
        }
    }

    /**
     * 从 Redis 获取模板
     */
    private String getTemplateFromRedis(String key) {
        if (redisTemplate == null) {
            return null;
        }

        try {
            String redisKey = REDIS_KEY_PREFIX + key;
            String template = redisTemplate.opsForValue().get(redisKey);
            return StringUtils.isNotEmpty(template) ? template : null;
        } catch (Exception e) {
            log.warn("读取Redis模板失败: {}", key, e);
            return null;
        }
    }

    /** Redis 无配置时返回 defaultTemplate */
    private String getPromptTemplateWithDefault(String templateKey, String defaultTemplate) {
        String fromRedis = getTemplateFromRedis(templateKey);
        return StringUtils.isNotEmpty(fromRedis) ? fromRedis : defaultTemplate;
    }

    /**
     * 验证模板键名是否有效
     */
    private boolean isValidKey(String key) {
        if (StringUtils.isEmpty(key)) {
            return false;
        }
        for (String validKey : TEMPLATE_KEYS) {
            if (validKey.equals(key)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 解析Double值
     */
    private Double parseDouble(Object value) {
        if (value == null) {
            return null;
        }
        if (value instanceof Double) {
            return (Double) value;
        }
        if (value instanceof Number) {
            return ((Number) value).doubleValue();
        }
        try {
            return Double.parseDouble(value.toString());
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    public AjaxResult listLlmProfiles() {
        try {
            Map<String, Object> data = new HashMap<>();
            if (redisTemplate == null) {
                data.put("redisAvailable", false);
                data.put("activeId", null);
                data.put("profiles", new ArrayList<>());
                return AjaxResult.success(data);
            }
            data.put("redisAvailable", true);
            JSONObject store = loadOrMigrateLlmStore();
            JSONObject profiles = store.getJSONObject("profiles");
            if (profiles == null) {
                profiles = new JSONObject();
            }
            String activeId = store.getString("activeId");
            List<Map<String, Object>> list = new ArrayList<>();
            for (String id : profiles.keySet()) {
                JSONObject p = profiles.getJSONObject(id);
                if (p == null) {
                    continue;
                }
                list.add(profileToSummaryMap(id, p, StringUtils.isNotEmpty(activeId) && activeId.equals(id)));
            }
            list.sort(Comparator.comparing(m -> String.valueOf(m.getOrDefault("name", "")), String.CASE_INSENSITIVE_ORDER));
            data.put("activeId", activeId);
            data.put("profiles", list);
            return AjaxResult.success(data);
        } catch (Exception e) {
            log.error("列出大模型配置失败", e);
            return AjaxResult.error("读取失败: " + e.getMessage());
        }
    }

    @Override
    public AjaxResult getLlmProfile(String id) {
        try {
            if (redisTemplate == null) {
                return AjaxResult.error("Redis未配置");
            }
            if (StringUtils.isEmpty(id)) {
                return AjaxResult.error("配置 id 不能为空");
            }
            JSONObject store = loadOrMigrateLlmStore();
            JSONObject profiles = store.getJSONObject("profiles");
            if (profiles == null || !profiles.containsKey(id)) {
                return AjaxResult.error("配置不存在");
            }
            JSONObject p = profiles.getJSONObject(id);
            Map<String, Object> row = new HashMap<>();
            row.put("id", id);
            row.put("name", StringUtils.trim(p.getString("name")));
            row.put("mode", normalizeLlmMode(p.getString("mode")));
            row.put("baseUrl", StringUtils.trim(p.getString("baseUrl")));
            row.put("model", StringUtils.trim(p.getString("model")));
            String rawKey = p.getString("apiKey");
            row.put("hasApiKey", StringUtils.isNotEmpty(StringUtils.trim(rawKey)));
            row.put("apiKeyMasked", maskLlmApiKey(rawKey));
            return AjaxResult.success(row);
        } catch (Exception e) {
            log.error("获取大模型配置失败", e);
            return AjaxResult.error("读取失败: " + e.getMessage());
        }
    }

    @Override
    public AjaxResult createLlmProfile(Map<String, Object> request) {
        try {
            if (redisTemplate == null) {
                return AjaxResult.error("Redis未配置，无法保存");
            }
            AjaxResult validation = validateLlmProfilePayload(request, true);
            if (validation != null) {
                return validation;
            }
            String name = request.get("name").toString().trim();
            String mode = request.get("mode").toString().trim().toLowerCase();
            String baseUrl = request.get("baseUrl") == null ? "" : request.get("baseUrl").toString().trim();
            String model = request.get("model") == null ? "" : request.get("model").toString().trim();

            JSONObject store = loadOrMigrateLlmStore();
            JSONObject profiles = store.getJSONObject("profiles");
            if (profiles == null) {
                profiles = new JSONObject();
                store.put("profiles", profiles);
            }
            String id = UUID.randomUUID().toString();
            JSONObject p = new JSONObject();
            p.put("name", name);
            p.put("mode", mode);
            p.put("baseUrl", baseUrl);
            p.put("model", model);
            String apiKey = "";
            Object apiKeyObj = request.get("apiKey");
            if (apiKeyObj != null && StringUtils.isNotEmpty(apiKeyObj.toString().trim())) {
                apiKey = apiKeyObj.toString().trim();
            }
            p.put("apiKey", apiKey);
            profiles.put(id, p);
            if (StringUtils.isEmpty(store.getString("activeId")) && profiles.size() == 1) {
                store.put("activeId", id);
            }
            persistLlmStore(store);
            log.info("新增大模型配置: id={}, name={}", id, name);
            Map<String, Object> res = new HashMap<>();
            res.put("id", id);
            return AjaxResult.success(res);
        } catch (Exception e) {
            log.error("新增大模型配置失败", e);
            return AjaxResult.error("保存失败: " + e.getMessage());
        }
    }

    @Override
    public AjaxResult updateLlmProfile(String id, Map<String, Object> request) {
        try {
            if (redisTemplate == null) {
                return AjaxResult.error("Redis未配置，无法保存");
            }
            if (StringUtils.isEmpty(id)) {
                return AjaxResult.error("配置 id 不能为空");
            }
            AjaxResult validation = validateLlmProfilePayload(request, true);
            if (validation != null) {
                return validation;
            }
            JSONObject store = loadOrMigrateLlmStore();
            JSONObject profiles = store.getJSONObject("profiles");
            if (profiles == null || !profiles.containsKey(id)) {
                return AjaxResult.error("配置不存在");
            }
            JSONObject p = profiles.getJSONObject(id);
            p.put("name", request.get("name").toString().trim());
            String mode = request.get("mode").toString().trim().toLowerCase();
            p.put("mode", mode);
            String baseUrl = request.get("baseUrl") == null ? "" : request.get("baseUrl").toString().trim();
            String model = request.get("model") == null ? "" : request.get("model").toString().trim();
            p.put("baseUrl", baseUrl);
            p.put("model", model);

            boolean clearApiKey = Boolean.TRUE.equals(request.get("clearApiKey"))
                    || "true".equalsIgnoreCase(String.valueOf(request.get("clearApiKey")));
            if (clearApiKey) {
                p.put("apiKey", "");
            } else {
                Object apiKeyObj = request.get("apiKey");
                if (apiKeyObj != null && StringUtils.isNotEmpty(apiKeyObj.toString().trim())) {
                    p.put("apiKey", apiKeyObj.toString().trim());
                }
            }
            persistLlmStore(store);
            log.info("更新大模型配置: id={}", id);
            return AjaxResult.success("保存成功");
        } catch (Exception e) {
            log.error("更新大模型配置失败", e);
            return AjaxResult.error("保存失败: " + e.getMessage());
        }
    }

    @Override
    public AjaxResult deleteLlmProfile(String id) {
        try {
            if (redisTemplate == null) {
                return AjaxResult.error("Redis未配置");
            }
            if (StringUtils.isEmpty(id)) {
                return AjaxResult.error("配置 id 不能为空");
            }
            JSONObject store = loadOrMigrateLlmStore();
            JSONObject profiles = store.getJSONObject("profiles");
            if (profiles == null || !profiles.containsKey(id)) {
                return AjaxResult.error("配置不存在");
            }
            profiles.remove(id);
            String activeId = store.getString("activeId");
            if (id.equals(activeId)) {
                if (profiles.isEmpty()) {
                    store.put("activeId", null);
                } else {
                    store.put("activeId", profiles.keySet().iterator().next());
                }
            }
            persistLlmStore(store);
            log.info("删除大模型配置: id={}", id);
            return AjaxResult.success("删除成功");
        } catch (Exception e) {
            log.error("删除大模型配置失败", e);
            return AjaxResult.error("删除失败: " + e.getMessage());
        }
    }

    @Override
    public AjaxResult setActiveLlmProfile(String id) {
        try {
            if (redisTemplate == null) {
                return AjaxResult.error("Redis未配置");
            }
            if (StringUtils.isEmpty(id)) {
                return AjaxResult.error("配置 id 不能为空");
            }
            JSONObject store = loadOrMigrateLlmStore();
            JSONObject profiles = store.getJSONObject("profiles");
            if (profiles == null || !profiles.containsKey(id)) {
                return AjaxResult.error("配置不存在");
            }
            store.put("activeId", id);
            persistLlmStore(store);
            log.info("已激活大模型配置: id={}", id);
            return AjaxResult.success("已切换为当前使用的配置");
        } catch (Exception e) {
            log.error("激活大模型配置失败", e);
            return AjaxResult.error("操作失败: " + e.getMessage());
        }
    }

    @Override
    public AjaxResult clearActiveLlmProfile() {
        try {
            if (redisTemplate == null) {
                return AjaxResult.error("Redis未配置");
            }
            JSONObject store = loadOrMigrateLlmStore();
            store.put("activeId", null);
            persistLlmStore(store);
            log.info("已取消激活大模型配置");
            return AjaxResult.success("已取消激活，Jarvis 将使用默认 Ollama");
        } catch (Exception e) {
            log.error("取消激活大模型配置失败", e);
            return AjaxResult.error("操作失败: " + e.getMessage());
        }
    }

    @Override
    public AjaxResult resetAllLlmConfig() {
        try {
            if (redisTemplate == null) {
                return AjaxResult.error("Redis未配置");
            }
            redisTemplate.delete(LLM_PROFILES_STORE_KEY);
            redisTemplate.delete(LLM_KEY_MODE);
            redisTemplate.delete(LLM_KEY_BASE_URL);
            redisTemplate.delete(LLM_KEY_API_KEY);
            redisTemplate.delete(LLM_KEY_MODEL);
            log.info("已清空所有大模型接入配置");
            return AjaxResult.success("已清空，Jarvis 将使用默认 Ollama");
        } catch (Exception e) {
            log.error("清空大模型配置失败", e);
            return AjaxResult.error("清除失败: " + e.getMessage());
        }
    }

    @Override
    public AjaxResult testLlmProfile(Map<String, Object> request) {
        try {
            String url = jarvisBaseUrl + "/jarvis/social-media/llm/test";
            JSONObject body = new JSONObject();
            if (request != null) {
                Object pid = request.get("profileId");
                if (pid != null && StringUtils.isNotEmpty(StringUtils.trim(pid.toString()))) {
                    body.put("profileId", pid.toString().trim());
                }
                Object msg = request.get("message");
                if (msg != null && StringUtils.isNotEmpty(StringUtils.trim(msg.toString()))) {
                    body.put("message", msg.toString().trim());
                }
            }
            log.info("转发 Jarvis LLM 测试: {}", url);
            String resp = HttpUtils.sendJsonPost(url, body.toJSONString());
            if (StringUtils.isEmpty(resp)) {
                return AjaxResult.error("Jarvis 返回为空，请检查 jarvis.server.jarvis-java.base-url 与 Jarvis 服务是否可达");
            }
            Object parsed = JSON.parse(resp);
            if (parsed instanceof JSONObject) {
                JSONObject jo = (JSONObject) parsed;
                if (Integer.valueOf(200).equals(jo.getInteger("code"))) {
                    return AjaxResult.success(jo.get("data"));
                }
                String errMsg = jo.getString("msg");
                return AjaxResult.error(StringUtils.isNotEmpty(errMsg) ? errMsg : "测试失败");
            }
            return AjaxResult.error("Jarvis 响应格式错误");
        } catch (Exception e) {
            log.error("大模型测试转发失败", e);
            return AjaxResult.error("测试失败: " + e.getMessage());
        }
    }

    private static String maskLlmApiKey(String secret) {
        if (StringUtils.isEmpty(StringUtils.trim(secret))) {
            return null;
        }
        String s = secret.trim();
        if (s.length() <= 4) {
            return "****";
        }
        return "****" + s.substring(s.length() - 4);
    }

    private static String normalizeLlmMode(String mode) {
        if (StringUtils.isEmpty(mode)) {
            return LLM_MODE_OLLAMA;
        }
        String m = mode.trim().toLowerCase();
        return LLM_MODE_OPENAI.equals(m) ? LLM_MODE_OPENAI : LLM_MODE_OLLAMA;
    }

    /**
     * @return 校验失败时返回 error AjaxResult，成功返回 null
     */
    private AjaxResult validateLlmProfilePayload(Map<String, Object> request, boolean requireName) {
        if (request == null) {
            return AjaxResult.error("请求体不能为空");
        }
        Object nameObj = request.get("name");
        if (requireName && (nameObj == null || StringUtils.isEmpty(StringUtils.trim(nameObj.toString())))) {
            return AjaxResult.error("请填写配置名称（用于区分多套）");
        }
        String mode = request.get("mode") == null ? "" : request.get("mode").toString().trim().toLowerCase();
        if (!LLM_MODE_OLLAMA.equals(mode) && !LLM_MODE_OPENAI.equals(mode)) {
            return AjaxResult.error("接入方式只能是 ollama 或 openai");
        }
        String baseUrl = request.get("baseUrl") == null ? "" : request.get("baseUrl").toString().trim();
        String model = request.get("model") == null ? "" : request.get("model").toString().trim();
        if (LLM_MODE_OPENAI.equals(mode)) {
            if (StringUtils.isEmpty(baseUrl)) {
                return AjaxResult.error("OpenAI 兼容模式须填写 Chat Completions 完整地址");
            }
            if (StringUtils.isEmpty(model)) {
                return AjaxResult.error("OpenAI 兼容模式须填写模型名称");
            }
        }
        return null;
    }

    private Map<String, Object> profileToSummaryMap(String id, JSONObject p, boolean active) {
        Map<String, Object> m = new HashMap<>();
        m.put("id", id);
        m.put("name", StringUtils.trim(p.getString("name")));
        m.put("mode", normalizeLlmMode(p.getString("mode")));
        m.put("baseUrl", StringUtils.trim(p.getString("baseUrl")));
        m.put("model", StringUtils.trim(p.getString("model")));
        String rawKey = p.getString("apiKey");
        m.put("hasApiKey", StringUtils.isNotEmpty(StringUtils.trim(rawKey)));
        m.put("apiKeyMasked", maskLlmApiKey(rawKey));
        m.put("active", active);
        return m;
    }

    private void persistLlmStore(JSONObject store) {
        redisTemplate.opsForValue().set(LLM_PROFILES_STORE_KEY, store.toJSONString());
    }

    /**
     * 读取存储；若无 JSON 则尝试从旧版单键迁移并写入。
     */
    private JSONObject loadOrMigrateLlmStore() {
        String raw = redisTemplate.opsForValue().get(LLM_PROFILES_STORE_KEY);
        if (StringUtils.isNotEmpty(raw)) {
            try {
                JSONObject o = JSON.parseObject(raw);
                if (o != null) {
                    if (!o.containsKey("profiles") || o.get("profiles") == null) {
                        o.put("profiles", new JSONObject());
                    }
                    return o;
                }
            } catch (Exception e) {
                log.warn("解析 LLM profiles_store 失败，将尝试迁移或重建", e);
            }
        }
        return migrateLegacyLlmToStore();
    }

    private JSONObject migrateLegacyLlmToStore() {
        JSONObject store = new JSONObject();
        JSONObject profiles = new JSONObject();
        String modeRaw = StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_MODE));
        String baseUrl = StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_BASE_URL));
        String model = StringUtils.trim(redisTemplate.opsForValue().get(LLM_KEY_MODEL));
        String apiKeyRaw = redisTemplate.opsForValue().get(LLM_KEY_API_KEY);
        String apiKey = StringUtils.trim(apiKeyRaw);

        boolean anyLegacy = StringUtils.isNotEmpty(modeRaw) || StringUtils.isNotEmpty(baseUrl)
                || StringUtils.isNotEmpty(model) || StringUtils.isNotEmpty(apiKey);
        if (!anyLegacy) {
            store.put("profiles", profiles);
            store.put("activeId", null);
            return store;
        }
        String id = "migrated-" + UUID.randomUUID().toString().replace("-", "").substring(0, 12);
        JSONObject p = new JSONObject();
        p.put("name", "迁移自旧版单套配置");
        p.put("mode", StringUtils.isEmpty(modeRaw) ? LLM_MODE_OLLAMA : modeRaw.toLowerCase());
        p.put("baseUrl", baseUrl == null ? "" : baseUrl);
        p.put("model", model == null ? "" : model);
        p.put("apiKey", apiKey == null ? "" : apiKey);
        profiles.put(id, p);
        store.put("profiles", profiles);
        store.put("activeId", id);
        persistLlmStore(store);
        redisTemplate.delete(LLM_KEY_MODE);
        redisTemplate.delete(LLM_KEY_BASE_URL);
        redisTemplate.delete(LLM_KEY_API_KEY);
        redisTemplate.delete(LLM_KEY_MODEL);
        log.info("已从旧版单键迁移大模型配置到多套存储");
        return store;
    }

    /**
     * 生成闲鱼文案：默认输出「代下单 + 教你下单」，可选额外生成「种草文案」。
     */
    @Override
    public Map<String, Object> generateXianyuWenan(Map<String, Object> request) {
        Map<String, Object> result = new HashMap<>();
        Map<String, Object> req = request == null ? new HashMap<String, Object>() : request;
        String title = asString(req.get("title"));
        String remark = asString(req.get("remark"));
        if (StringUtils.isEmpty(title)) {
            // 兼容只传新字段的调用方式
            title = asString(req.get("goods_title"));
        }
        if (StringUtils.isEmpty(title) || StringUtils.isEmpty(title.trim())) {
            result.put("success", false);
            result.put("error", "商品标题不能为空");
            return result;
        }
        String cleanTitle = cleanTitleOrRemark(title.trim());
        String cleanRemark = StringUtils.isNotEmpty(remark) ? cleanTitleOrRemark(remark.trim()) : "";

        boolean generateSeed = isTrue(req.get("generateSeedNote"));
        if (generateSeed) {
            if (StringUtils.isEmpty(asString(req.get("goods_title")))) {
                req.put("goods_title", cleanTitle);
            }
            if (StringUtils.isEmpty(asString(req.get("goods_model")))) {
                req.put("goods_model", cleanRemark);
            }
            String seedError = validateSeedRequiredFields(req);
            if (seedError != null) {
                result.put("success", false);
                result.put("error", seedError);
                return result;
            }
        }

        String wenanBase = getPromptTemplateWithDefault("xianyu:wenan_base", DEFAULT_XIANYU_WENAN_BASE);
        String jiaonixiadanExtra = getPromptTemplateWithDefault("xianyu:jiaonixiadan_extra", DEFAULT_XIANYU_JIAONIXIADAN_EXTRA);

        // 标题行
        StringBuilder daixiadanBuilder = new StringBuilder();
        daixiadanBuilder.append(cleanTitle).append("\n");
        // 型号行（可选）
        if (StringUtils.isNotEmpty(cleanRemark)) {
            daixiadanBuilder.append("型号：").append(cleanRemark).append("\n");
        }
        daixiadanBuilder.append(wenanBase);

        // 教你下单版
        StringBuilder jiaonixiadanBuilder = new StringBuilder();
        jiaonixiadanBuilder.append(cleanTitle).append("\n");
        if (StringUtils.isNotEmpty(cleanRemark)) {
            jiaonixiadanBuilder.append("型号：").append(cleanRemark).append("\n");
        }

        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
        String dateStr = sdf.format(new java.util.Date());
        jiaonixiadanBuilder.append("更新日期：").append(dateStr).append(jiaonixiadanExtra);

        result.put("success", true);
        result.put("daixiadan", daixiadanBuilder.toString());
        result.put("jiaonixiadan", jiaonixiadanBuilder.toString());

        if (generateSeed) {
            try {
                String seedPrompt = buildSeedPrompt(req);
                String seedNote = callJarvisLlm(seedPrompt, asString(req.get("profileId")));
                if (StringUtils.isEmpty(seedNote)) {
                    throw new RuntimeException("种草文案生成返回为空");
                }
                result.put("seedNote", seedNote.trim());
            } catch (Exception e) {
                log.error("生成闲鱼种草文案失败", e);
                result.put("seedNote", "");
                result.put("seedNoteError", "种草文案生成失败: " + e.getMessage());
            }
        }
        return result;
    }

    private String buildSeedPrompt(Map<String, Object> request) {
        String template = getPromptTemplateWithDefault("xianyu:seed_note_prompt", DEFAULT_XIANYU_SEED_NOTE_PROMPT);

        String goodsTitle = asString(request.get("goods_title"));
        String goodsModel = asString(request.get("goods_model"));
        String goodsType = asString(request.get("goods_type"));

        String goodsBrand = asString(request.get("goods_brand"));
        String channelSource = asString(request.get("channel_source"));
        String officialPrice = asString(request.get("official_price"));
        String sellPrice = asString(request.get("sell_price"));
        String warranty = asString(request.get("warranty"));
        String deliveryInstall = asString(request.get("delivery_install"));

        String channelFinal = StringUtils.isNotEmpty(channelSource) ? channelSource : "品牌授权经销商直供";
        String warrantyFinal = StringUtils.isNotEmpty(warranty) ? warranty : "品牌官方全国联保，和官方旗舰店享受同等售后权益";
        String deliveryFinal = StringUtils.isNotEmpty(deliveryInstall) ? deliveryInstall : "全国包邮送货入户，乡镇可达";
        String officialFinal = StringUtils.isNotEmpty(officialPrice) ? officialPrice : "比官方旗舰店优惠力度大";
        String sellFinal = StringUtils.isNotEmpty(sellPrice) ? sellPrice : "私信询价（到手更划算）";

        return template
                .replace("{{goods_title}}", goodsTitle)
                .replace("{{goods_model}}", goodsModel)
                .replace("{{goods_type}}", goodsType)
                .replace("{{goods_brand}}", StringUtils.isNotEmpty(goodsBrand) ? goodsBrand : "")
                .replace("{{channel_source}}", channelFinal)
                .replace("{{official_price}}", officialFinal)
                .replace("{{sell_price}}", sellFinal)
                .replace("{{warranty}}", warrantyFinal)
                .replace("{{delivery_install}}", deliveryFinal);
    }

    private String validateSeedRequiredFields(Map<String, Object> request) {
        if (request == null) {
            return "请求体不能为空";
        }
        if (StringUtils.isEmpty(asString(request.get("goods_title")))) {
            return "生成种草文案时，goods_title 不能为空";
        }
        if (StringUtils.isEmpty(asString(request.get("goods_model")))) {
            return "生成种草文案时，goods_model 不能为空";
        }
        if (StringUtils.isEmpty(asString(request.get("goods_type")))) {
            return "生成种草文案时，goods_type 不能为空";
        }
        return null;
    }

    private String callJarvisLlm(String message, String profileId) {
        String url = jarvisBaseUrl + "/jarvis/social-media/llm/test";
        JSONObject body = new JSONObject();
        body.put("message", message);
        if (StringUtils.isNotEmpty(profileId)) {
            body.put("profileId", profileId);
        }
        String resp = HttpUtils.sendJsonPost(url, body.toJSONString());
        if (StringUtils.isEmpty(resp)) {
            throw new RuntimeException("Jarvis 返回为空，请检查服务可达性");
        }
        Object parsed = JSON.parse(resp);
        if (!(parsed instanceof JSONObject)) {
            throw new RuntimeException("Jarvis 响应格式错误");
        }
        JSONObject jo = (JSONObject) parsed;
        if (!Integer.valueOf(200).equals(jo.getInteger("code"))) {
            throw new RuntimeException(jo.getString("msg"));
        }
        Object dataObj = jo.get("data");
        if (!(dataObj instanceof JSONObject)) {
            throw new RuntimeException("Jarvis data 结构异常");
        }
        JSONObject data = (JSONObject) dataObj;
        if (!Boolean.TRUE.equals(data.getBoolean("success"))) {
            throw new RuntimeException(data.getString("error"));
        }
        return data.getString("reply");
    }

    private static boolean isTrue(Object value) {
        return Boolean.TRUE.equals(value) || "true".equalsIgnoreCase(String.valueOf(value));
    }

    private static String asString(Object value) {
        if (value == null) {
            return "";
        }
        return StringUtils.trim(value.toString());
    }

    /**
     * 清洗标题/型号中的敏感词（正则来自可配置模板 xianyu:title_clean_regex）
     */
    private String cleanTitleOrRemark(String text) {
        if (text == null) {
            return "";
        }
        String regex = getPromptTemplateWithDefault("xianyu:title_clean_regex", DEFAULT_XIANYU_TITLE_CLEAN_REGEX);
        try {
            return text.replaceAll(regex, "");
        } catch (Exception e) {
            log.warn("标题清洗正则执行失败，使用内置默认: {}", e.getMessage());
            return text.replaceAll(DEFAULT_XIANYU_TITLE_CLEAN_REGEX, "");
        }
    }
}

