package com.ruoyi.jarvis.service.impl;

import com.ruoyi.jarvis.domain.SuperAdmin;
import com.ruoyi.jarvis.domain.dto.WeComChatSession;
import com.ruoyi.jarvis.domain.dto.WeComInboundRequest;
import com.ruoyi.jarvis.domain.dto.WeComInboundResult;
import com.ruoyi.jarvis.service.IInstructionService;
import com.ruoyi.jarvis.service.ILogisticsService;
import com.ruoyi.jarvis.service.IWeComChatSessionService;
import com.ruoyi.jarvis.service.IWeComInboundService;
import com.ruoyi.jarvis.service.SuperAdminService;
import com.ruoyi.jarvis.util.WeComUtf8ChunkUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * LinPingFan：全部指令；其他人员：须在超级管理员中识别为本人（wxid=企微 UserID，**或** 企微 UserID 出现在 touser 逗号分隔列表中），且仅「京*」指令 + 京东分享物流链接流程；
 * 例外：以「单」或「开始」开头且含「分销标记」的录单正文优先于物流（不进入 3.cn 多轮、不占用物流监听）。
 * 以「开」或「慢开」开头且正文含 11 位手机号（1 开头）：TG 查询默认先被动回复「收到电话」，结果经 wxSend 主动推送；本地库命中仍为被动全文。
 * 多轮会话使用 Redis（{@link WeComChatSession}，键 interaction_state:wecom:{FromUserName}），与旧版「开通礼金」interaction_state 思路一致。
 * 回复正文按 UTF-8 每段至多 2048 字节拆分：首段被动回复，其余主动推送（同一次用户消息、不重复触发查询）。
 */
@Service
public class WeComInboundServiceImpl implements IWeComInboundService {

    private static final Logger log = LoggerFactory.getLogger(WeComInboundServiceImpl.class);

    public static final String WE_COM_SUPER_USER_ID = "LinPingFan";

    private static final Pattern JD_3CN = Pattern.compile("https://3\\.cn/[A-Za-z0-9\\-]+");

    /** 无超级管理员配置 */
    private static String replyPermissionDenied() {
        return "「权限说明」\n\n"
                + "当前企微账号尚未授权使用本机器人。\n\n"
                + "请管理员在后台打开「超级管理员」，任选一种方式：\n"
                + "· 将您的企微 UserID 填入 wxid；或\n"
                + "· 将您的 UserID 写入某一行的 touser（多个用英文逗号分隔）。";
    }

    /** 已授权但非 LinPingFan：可发送的内容范围 */
    private static String replyGeneralUserScopeHint() {
        return "「发送范围」\n\n"
                + "当前账号支持：\n"
                + "· 「京」开头的统计、订单类指令（可先发「京菜单」查看列表）\n"
                + "· 含 3.cn 的京东物流分享：先发链接，再发备注\n"
                + "· 京外物列表 / 京外物删 — 查询或删除外部分享链物流登记\n"
                + "· 以「单」或「开始」开头，且含「分销标记」的录单正文\n"
                + "· 以「开」或「慢开」开头且含手机号的查询\n\n"
                + "如需其他指令，请联系管理员。";
    }

    /** 用户取消物流多轮 */
    private static String replyLogisticsCancelled() {
        return "「物流录入」已取消\n\n"
                + "可重新发送含 3.cn 的分享链接，开始新的一轮。";
    }

    /** 已收到链接，等待备注 */
    private static String replyLogisticsWaitRemark(String shortUrl) {
        String link = shortUrl != null ? shortUrl : "";
        return "「物流链接」已收到\n"
                + link + "\n\n"
                + "请在下一条消息中发送该单的备注。\n"
                + "若需放弃本轮，请回复「取消」或「取消录入」。";
    }

    /** 备注已提交并入队 */
    private static String replyLogisticsRemarkDone() {
        return "「物流备注」已登记\n\n"
                + "已加入查询队列，约每 15 分钟扫描并推送结果（与订单物流任务一致）。\n"
                + "本轮录入已结束，可继续发录单、指令或新的物流链接。";
    }

    @Resource
    private SuperAdminService superAdminService;
    @Resource
    private IInstructionService instructionService;
    @Resource
    private ILogisticsService logisticsService;
    @Resource
    private IWeComChatSessionService weComChatSessionService;
    @Resource
    private OpenPhoneForwardService openPhoneForwardService;

    @Override
    public WeComInboundResult handleInbound(WeComInboundRequest req) {
        if (req == null || !StringUtils.hasText(req.getFromUserName())) {
            return WeComInboundResult.empty();
        }
        String from = req.getFromUserName().trim();
        String content = req.getContent() != null ? req.getContent() : "";

        SuperAdmin row = superAdminService.selectSuperAdminByWecomUserId(from);
        boolean isSuper = WE_COM_SUPER_USER_ID.equals(from);
        if (!isSuper && row == null) {
            return WeComInboundResult.passiveOnly(replyPermissionDenied());
        }

        String openPhoneReply = openPhoneForwardService.tryReply(req);
        if (openPhoneReply != null) {
            return toChunkedInboundResult(openPhoneReply);
        }

        final boolean danRecordPriority = isDanRecordPriorityOverLogistics(content);

        WeComChatSession session = weComChatSessionService.get(from);
        if (session != null && !session.matchLogisticsWaitRemark()) {
            weComChatSessionService.delete(from);
            session = null;
        }

        if (session != null && session.matchLogisticsWaitRemark()) {
            String t = content.trim();
            if ("取消".equals(t) || "取消录入".equals(t)) {
                weComChatSessionService.delete(from);
                return WeComInboundResult.passiveOnly(replyLogisticsCancelled());
            }
            if (danRecordPriority) {
                weComChatSessionService.delete(from);
                log.info("企微用户 {} 在录单优先下退出物流备注会话", from);
            } else if (isJdShareLogisticsMessage(content)) {
                String url = extractJd3cnUrl(content);
                if (url != null && url.equals(session.getLogisticsUrl())) {
                    weComChatSessionService.delete(from);
                    String touser = resolveTouser(row, isSuper, from);
                    log.info("企微物流会话备注(含同款链接)提交 user={} url={} remarkLen={}", from, url, t.length());
                    logisticsService.enqueueShareLinkForScan(url, content.trim(), touser, from);
                    return WeComInboundResult.passiveOnly(replyLogisticsRemarkDone());
                }
                if (url != null) {
                    weComChatSessionService.put(from, WeComChatSession.startLogisticsWaitRemark(url));
                    return WeComInboundResult.passiveOnly(replyLogisticsWaitRemark(url));
                }
            } else if (t.startsWith("京")) {
                weComChatSessionService.delete(from);
                log.info("企微用户 {} 在京指令下中断物流备注会话", from);
            } else {
                String url = session.getLogisticsUrl();
                weComChatSessionService.delete(from);
                String touser = resolveTouser(row, isSuper, from);
                log.info("企微物流会话提交备注 user={} url={} remarkLen={}", from, url, t.length());
                logisticsService.enqueueShareLinkForScan(url, content.trim(), touser, from);
                return WeComInboundResult.passiveOnly(replyLogisticsRemarkDone());
            }
        }

        if (!danRecordPriority && isJdShareLogisticsMessage(content)) {
            String url = extractJd3cnUrl(content);
            if (url != null) {
                weComChatSessionService.put(from, WeComChatSession.startLogisticsWaitRemark(url));
                log.info("企微物流多轮会话已创建 user={} url={}", from, url);
                return WeComInboundResult.passiveOnly(replyLogisticsWaitRemark(url));
            }
        }

        if (!isSuper) {
            String cmd = content.trim().replaceFirst("^\uFEFF", "");
            if (!cmd.startsWith("京") && !danRecordPriority && !cmd.startsWith("开") && !cmd.startsWith("慢开")) {
                return WeComInboundResult.passiveOnly(replyGeneralUserScopeHint());
            }
        }

        List<String> parts = instructionService.execute(content, false, isSuper, from);
        if (parts == null || parts.isEmpty()) {
            return WeComInboundResult.empty();
        }
        if (parts.size() == 1) {
            return toChunkedInboundResult(parts.get(0));
        }
        List<String> headChunks = WeComUtf8ChunkUtil.splitUtf8Chunks(parts.get(0), WeComUtf8ChunkUtil.WE_COM_TEXT_MAX_UTF8_BYTES);
        String passive = headChunks.isEmpty() ? "" : headChunks.get(0);
        List<String> active = new ArrayList<>();
        for (int h = 1; h < headChunks.size(); h++) {
            active.add(headChunks.get(h));
        }
        for (int i = 1; i < parts.size(); i++) {
            String p = parts.get(i);
            if (p == null) {
                continue;
            }
            for (String chunk : WeComUtf8ChunkUtil.splitUtf8Chunks(p, WeComUtf8ChunkUtil.WE_COM_TEXT_MAX_UTF8_BYTES)) {
                active.add(chunk);
            }
        }
        return new WeComInboundResult(passive, active);
    }

    /**
     * 首段 ≤2048 UTF-8 字节走被动回复，其余走 wxSend 主动推送（同一次用户消息内顺序下发，不重复计费）。
     */
    private static WeComInboundResult toChunkedInboundResult(String fullText) {
        List<String> chunks = WeComUtf8ChunkUtil.splitUtf8Chunks(fullText, WeComUtf8ChunkUtil.WE_COM_TEXT_MAX_UTF8_BYTES);
        if (chunks.isEmpty()) {
            return WeComInboundResult.passiveOnly("");
        }
        if (chunks.size() == 1) {
            return WeComInboundResult.passiveOnly(chunks.get(0));
        }
        return new WeComInboundResult(chunks.get(0), new ArrayList<>(chunks.subList(1, chunks.size())));
    }

    /**
     * 录单正文（指令层走「单…」写库）优先于物流：与 {@link InstructionServiceImpl} 新模板一致。
     */
    private static boolean isDanRecordPriorityOverLogistics(String text) {
        if (!StringUtils.hasText(text)) {
            return false;
        }
        String t = text.trim().replaceFirst("^\uFEFF", "");
        if (!t.contains("分销标记")) {
            return false;
        }
        if (t.startsWith("单") || t.startsWith("开始")) {
            return true;
        }
        return t.contains("单：")
            && (t.contains("下单链接（必须用这个）") || t.contains("—————————") || t.contains("下单地址（注意带分机）"));
    }

    private static boolean isJdShareLogisticsMessage(String text) {
        if (!StringUtils.hasText(text)) {
            return false;
        }
        return text.contains("https://3.cn/") || text.contains("http://3.cn/");
    }

    private static String extractJd3cnUrl(String text) {
        Matcher m = JD_3CN.matcher(text);
        if (m.find()) {
            return m.group();
        }
        return null;
    }

    /**
     * 分享链推送目标：超级管理员表中的 touser（可多选）优先；否则回退为发消息成员的企微 UserID。
     * 仅配 wxid 未配 touser 时，过去会导致请求不带接收人、依赖远端默认路由，易出现「看不到推送」。
     */
    private String resolveTouser(SuperAdmin row, boolean isSuper, String fromWecomUserId) {
        if (row != null && StringUtils.hasText(row.getTouser())) {
            return row.getTouser().trim();
        }
        if (isSuper) {
            SuperAdmin ping = superAdminService.selectSuperAdminByWecomUserId(WE_COM_SUPER_USER_ID);
            if (ping != null && StringUtils.hasText(ping.getTouser())) {
                return ping.getTouser().trim();
            }
        }
        if (StringUtils.hasText(fromWecomUserId)) {
            return fromWecomUserId.trim();
        }
        return null;
    }
}
