package com.ruoyi.jarvis.wecom;

import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.jarvis.domain.ErpGoofishOrder;
import com.ruoyi.jarvis.mapper.ErpGoofishOrderMapper;
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.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 调用 wxSend 闲鱼自建应用文本推送，与 /wx/send/pdd 一致：Header vanToken + JSON（title、text、touser）。
 */
@Component
public class WxSendGoofishNotifyClient {

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

    /** 与企微 message/send 文本 content 上限对齐，预留余量 */
    private static final int CONTENT_MAX = 2000;

    @Value("${jarvis.wecom.wxsend-base-url:}")
    private String wxsendBaseUrl;

    /** 与 wxSend TokenUtil 校验一致，请求头 vanToken */
    @Value("${jarvis.wecom.wxsend-van-token:}")
    private String wxsendVanToken;

    /**
     * 接收通知的成员 UserID（企微管理后台可见），多个用英文逗号或 | 分隔；
     * 发往 wxSend 时会规范为 touser 的 user1|user2 形式。留空表示不推送。
     */
    @Value("${jarvis.wecom.goofish-notify-touser:}")
    private String goofishNotifyTouser;

    @Autowired(required = false)
    private ErpGoofishOrderMapper erpGoofishOrderMapper;

    /**
     * 闲鱼订单事件通知 wxSend（由 wxSend 转企微应用消息）。
     *
     * @param orderNo   闲鱼订单号
     * @param eventType ORDER_SYNC / LOGISTICS_SYNC / SHIP
     * @param source    NOTIFY、LIST、DETAIL_REFRESH 等
     * @param message   已截断的说明文案
     */
    public void notifyGoofishEvent(String orderNo, String eventType, String source, String message) {
        if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(wxsendVanToken)) {
            return;
        }
        String toUser = buildTouserParam(goofishNotifyTouser);
        if (!StringUtils.hasText(toUser)) {
            return;
        }
        String content = composeNotifyContent(orderNo, eventType, source, message);
        if (!StringUtils.hasText(content)) {
            return;
        }
        try {
            String base = normalizeBase(wxsendBaseUrl);
            String url = base + "/wx/send/goofish";
            postMessageReturnsOk(url, wxsendVanToken, "", content, toUser);
        } catch (Exception e) {
            log.warn("wxSend /wx/send/goofish 失败 orderNo={} err={}", orderNo, e.toString());
        }
    }

    /**
     * 京东物流扫描等：发往企微「闲鱼」自建应用，请求体与 PDD 推送一致（title + text + touser）。
     *
     * @param optionalToUser 非空时用与 logistics.push.touser.* 相同的成员 ID（逗号/| 亦可）；空则用 goofish-notify-touser
     * @param title          与 {@code LogisticsServiceImpl} 推 PDD 时的 title 一致，可为空
     * @param textBody       正文（对应 PDD 的 text）
     * @return wxSend 业务 code=200 为 true；未配置 token 或 touser 为空返回 false
     */
    public boolean pushGoofishAgentText(String optionalToUser, String title, String textBody) {
        if (!StringUtils.hasText(wxsendBaseUrl) || !StringUtils.hasText(wxsendVanToken)) {
            log.debug("wxSend 闲鱼应用物流推送跳过：未配置 wxsend-base-url 或 wxsend-van-token");
            return false;
        }
        String rawTouser = StringUtils.hasText(optionalToUser) ? optionalToUser : goofishNotifyTouser;
        String toUser = buildTouserParam(rawTouser);
        if (!StringUtils.hasText(toUser)) {
            log.warn("wxSend 闲鱼应用物流推送跳过：无接收人（请配置 jarvis.wecom.goofish-notify-touser 或 logistics.push.touser）");
            return false;
        }
        String t = title != null ? title : "";
        String body = textBody != null ? textBody : "";
        if (t.length() > CONTENT_MAX) {
            t = t.substring(0, CONTENT_MAX - 1) + "…";
            body = "";
        } else {
            int overhead = StringUtils.hasText(t) ? t.length() + 1 : 0;
            int maxBody = Math.max(0, CONTENT_MAX - overhead);
            if (body.length() > maxBody) {
                body = body.substring(0, Math.max(0, maxBody - 1)) + "…";
            }
        }
        try {
            String base = normalizeBase(wxsendBaseUrl);
            String url = base + "/wx/send/goofish";
            return postMessageReturnsOk(url, wxsendVanToken, t, body, toUser);
        } catch (Exception e) {
            log.warn("wxSend /wx/send/goofish 物流全文失败 err={}", e.toString());
            return false;
        }
    }

    /**
     * 企微短通知：标题 + 型号/地址/日期 + 闲鱼订单号 + 事件明细。
     */
    private String composeNotifyContent(String orderNo, String eventType, String source, String message) {
        String snapshot = buildOrderSnapshotBlock(orderNo);
        return assembleContent(orderNo, eventType, source, message, snapshot);
    }

    /**
     * 从库中按闲鱼订单号取一条较「全」的快照，拼装通用上下文（所有订单类通知共用）。
     */
    private String buildOrderSnapshotBlock(String orderNo) {
        if (!StringUtils.hasText(orderNo) || erpGoofishOrderMapper == null) {
            return "";
        }
        List<ErpGoofishOrder> list;
        try {
            list = erpGoofishOrderMapper.selectByGoofishOrderNo(orderNo.trim());
        } catch (Exception e) {
            log.debug("通知上下文查询闲鱼订单跳过 orderNo={} err={}", orderNo, e.toString());
            return "";
        }
        if (list == null || list.isEmpty()) {
            return "";
        }
        ErpGoofishOrder r = pickPreferredGoofishSnapshot(list);
        if (r == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        String model = resolveModelLine(r);
        if (StringUtils.hasText(model)) {
            sb.append("型号：").append(clampOneLine(model, 200)).append('\n');
        }
        String addr = buildDisplayAddress(r);
        if (StringUtils.hasText(addr)) {
            sb.append("收货地址：").append(clampOneLine(addr, 500)).append('\n');
        }
        if (r.getCreateTime() != null) {
            sb.append("入库时间：")
                .append(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, r.getCreateTime()))
                .append('\n');
        }
        String modified = formatPlatformModifyTime(r.getModifyTime());
        if (StringUtils.hasText(modified)) {
            sb.append("平台更新：").append(modified).append('\n');
        }
        return sb.toString();
    }

    private static ErpGoofishOrder pickPreferredGoofishSnapshot(List<ErpGoofishOrder> list) {
        ErpGoofishOrder best = list.get(0);
        int bs = snapshotRichness(best);
        for (int i = 1; i < list.size(); i++) {
            ErpGoofishOrder x = list.get(i);
            int s = snapshotRichness(x);
            if (s > bs) {
                best = x;
                bs = s;
            }
        }
        return best;
    }

    private static int snapshotRichness(ErpGoofishOrder r) {
        if (r == null) {
            return 0;
        }
        int s = 0;
        if (r.getJdOrderId() != null) {
            s += 2;
        }
        if (StringUtils.hasText(trim(r.getJdModelNumber()))) {
            s += 5;
        }
        if (StringUtils.hasText(trim(r.getJdAddress()))) {
            s += 4;
        }
        if (StringUtils.hasText(trim(r.getReceiverAddress()))) {
            s += 2;
        }
        if (StringUtils.hasText(trim(r.getGoodsTitle()))) {
            s += 1;
        }
        return s;
    }

    private static String resolveModelLine(ErpGoofishOrder r) {
        String m = trim(r.getJdModelNumber());
        if (StringUtils.hasText(m)) {
            return m;
        }
        return abbrevTitle(r.getGoodsTitle(), 180);
    }

    private static String abbrevTitle(String title, int max) {
        String t = trim(title);
        if (!StringUtils.hasText(t)) {
            return "";
        }
        if (t.length() <= max) {
            return t;
        }
        return t.substring(0, Math.max(0, max - 1)) + '…';
    }

    private static String buildDisplayAddress(ErpGoofishOrder r) {
        if (r == null) {
            return "";
        }
        String jd = trim(r.getJdAddress());
        if (StringUtils.hasText(jd)) {
            return jd;
        }
        String regionLine = trim(r.getReceiverRegion());
        if (!StringUtils.hasText(regionLine)) {
            regionLine = joinWithSpace(
                r.getRecvProvName(), r.getRecvCityName(), r.getRecvAreaName(), r.getRecvTownName());
        }
        String addr = trim(r.getReceiverAddress());
        if (StringUtils.hasText(regionLine) && StringUtils.hasText(addr)) {
            return regionLine + " " + addr;
        }
        return StringUtils.hasText(addr) ? addr : regionLine;
    }

    private static String joinWithSpace(String... parts) {
        if (parts == null) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        for (String p : parts) {
            String t = trim(p);
            if (!StringUtils.hasText(t)) {
                continue;
            }
            if (sb.length() > 0) {
                sb.append(' ');
            }
            sb.append(t);
        }
        return sb.toString();
    }

    private static String formatPlatformModifyTime(Long modifyTime) {
        if (modifyTime == null) {
            return "";
        }
        long ms = modifyTime > 9_999_999_999L ? modifyTime : modifyTime * 1000L;
        return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, new Date(ms));
    }

    private static String clampOneLine(String s, int max) {
        String t = trim(s);
        if (!StringUtils.hasText(t)) {
            return "";
        }
        t = t.replace('\r', ' ').replace('\n', ' ').replace('，', ' ');
        t = t.trim().replaceAll("\\s+", " ");
        if (t.length() <= max) {
            return t;
        }
        return t.substring(0, Math.max(0, max - 1)) + '…';
    }

    private static String trim(String x) {
        return x == null ? "" : x.trim();
    }

    private static String assembleContent(String orderNo, String eventType, String source, String message,
        String snapshotBlock) {
        String on = orderNo != null ? orderNo : "-";
        String head = resolveNotifyHeadline(eventType, message);
        String detail = normalizeNotifyDetailLines(eventType, message);
        StringBuilder sb = new StringBuilder();
        sb.append(head).append('\n');
        if (StringUtils.hasText(snapshotBlock)) {
            sb.append(snapshotBlock);
        }
        sb.append("闲鱼订单号：").append(on).append('\n');
        if (StringUtils.hasText(detail)) {
            sb.append(detail);
        }
        String s = sb.toString();
        if (s.length() > CONTENT_MAX) {
            return s.substring(0, CONTENT_MAX - 1) + "…";
        }
        return s;
    }

    private static String resolveNotifyHeadline(String eventType, String message) {
        String m = message != null ? message : "";
        String t = eventType != null ? eventType.trim() : "";
        if ("SHIP".equals(t)) {
            if (m.contains("失败") || m.contains("异常") || m.contains("缺地址")) {
                return "发货失败";
            }
            return "发货";
        }
        if ("LOGISTICS_SYNC".equals(t)) {
            return "物流";
        }
        if ("ORDER_SYNC".equals(t)) {
            if (m.startsWith("新订单") || m.contains("新订单入库")) {
                return "新订单";
            }
            if (looksRefundOnlyDiffLine(m)) {
                return "退款";
            }
            return "订单";
        }
        return StringUtils.hasText(t) ? t : "订单";
    }

    /** ORDER_SYNC 正文里是否仅有退款状态变更（单行或多行中的唯一语义块） */
    private static boolean looksRefundOnlyDiffLine(String message) {
        if (!StringUtils.hasText(message) || message.contains("新订单")) {
            return false;
        }
        String normalized = message.replace('；', '\n').trim();
        String[] lines = normalized.split("\n");
        boolean sawRefund = false;
        boolean sawOrder = false;
        for (String raw : lines) {
            String line = raw.trim();
            if (line.isEmpty()) {
                continue;
            }
            if (line.startsWith("退款状态")) {
                sawRefund = true;
                continue;
            }
            if (line.startsWith("订单状态")) {
                sawOrder = true;
                continue;
            }
            return false;
        }
        return sawRefund && !sawOrder;
    }

    /**
     * 将内部摘要句转为「字段：值」行；箭头统一为「 → 」。
     */
    private static String normalizeNotifyDetailLines(String eventType, String message) {
        if (!StringUtils.hasText(message)) {
            return "";
        }
        String t = eventType != null ? eventType.trim() : "";
        String m = message.trim();

        if ("SHIP".equals(t)) {
            if (m.startsWith("发货成功")) {
                StringBuilder details = new StringBuilder();
                String wb = extractCommaField(m, "运单 ");
                if (StringUtils.hasText(wb)) {
                    details.append("物流单号：").append(normalizeArrowSpaces(wb)).append('\n');
                }
                String os = extractCommaField(m, "订单状态 ");
                if (StringUtils.hasText(os)) {
                    details.append("订单状态：").append(normalizeArrowSpaces(os)).append('\n');
                }
                String rs = extractCommaField(m, "退款状态 ");
                if (StringUtils.hasText(rs)) {
                    details.append("退款状态：").append(normalizeArrowSpaces(rs)).append('\n');
                }
                if (details.length() > 0) {
                    return details.toString();
                }
            }
            if (m.startsWith("发货失败") || m.startsWith("发货异常")) {
                int idx = m.indexOf('：');
                String reason = idx >= 0 && idx < m.length() - 1 ? m.substring(idx + 1).trim() : m;
                return "原因：" + normalizeArrowSpaces(reason) + "\n";
            }
        }

        if ("ORDER_SYNC".equals(t)) {
            // 新订单入库，订单状态 X，退款状态 Y
            if (m.contains("新订单入库") && m.contains("订单状态 ") && m.contains("退款状态 ")) {
                String os = substringBetweenPrefixes(m, "订单状态 ", "，退款状态");
                String rs = substringAfterPrefix(m, "退款状态 ");
                StringBuilder sb = new StringBuilder();
                if (StringUtils.hasText(os)) {
                    sb.append("订单状态：").append(os.trim()).append('\n');
                }
                if (StringUtils.hasText(rs)) {
                    sb.append("退款状态：").append(rs.trim()).append('\n');
                }
                return sb.toString();
            }
            // 订单状态 A → B；退款状态 …（仅输出消息里出现的块）
            return formatOrderSyncSemicolonParts(m);
        }

        if ("LOGISTICS_SYNC".equals(t)) {
            return formatLogisticsParts(m);
        }

        return normalizeArrowSpaces(m) + "\n";
    }

    private static String formatOrderSyncSemicolonParts(String m) {
        String[] parts = m.split("；");
        StringBuilder sb = new StringBuilder();
        for (String p : parts) {
            String line = kvLineOrderOrRefund(p.trim());
            if (line != null) {
                sb.append(line).append('\n');
            }
        }
        return sb.toString();
    }

    private static String kvLineOrderOrRefund(String segment) {
        if (!StringUtils.hasText(segment)) {
            return null;
        }
        if (segment.startsWith("订单状态 ")) {
            return "订单状态：" + normalizeArrowSpaces(segment.substring("订单状态 ".length()).trim());
        }
        if (segment.startsWith("退款状态 ")) {
            return "退款状态：" + normalizeArrowSpaces(segment.substring("退款状态 ".length()).trim());
        }
        return null;
    }

    private static String formatLogisticsParts(String m) {
        String[] parts = m.split("；");
        StringBuilder sb = new StringBuilder();
        for (String p : parts) {
            String line = logisticsSegmentToKv(p.trim());
            if (line != null) {
                sb.append(line).append('\n');
            }
        }
        return sb.toString();
    }

    /** 本地/平台运单 → 对用户展示为物流单号；其它变更行仍可保留技术性标签 */
    private static String logisticsSegmentToKv(String segment) {
        if (!StringUtils.hasText(segment)) {
            return null;
        }
        if (segment.startsWith("本地运单 ") || segment.startsWith("平台运单 ")) {
            int cut = segment.indexOf(' ');
            String rest = normalizeArrowSpaces(segment.substring(cut + 1).trim());
            return "物流单号：" + rest;
        }
        String kv = logisticLabelToColon(segment);
        return kv != null ? kv : (normalizeArrowSpaces(segment));
    }

    private static String logisticLabelToColon(String segment) {
        String[] prefixes = {"快递编码 ", "快递名称 "};
        for (String pref : prefixes) {
            if (segment.startsWith(pref)) {
                String label = pref.trim();
                return label.substring(0, label.length() - 1) + "："
                    + normalizeArrowSpaces(segment.substring(pref.length()).trim());
            }
        }
        return null;
    }

    private static String normalizeArrowSpaces(String s) {
        if (s == null) {
            return "";
        }
        return s.replace("→", " → ").replace("  →", " →").replace("→  ", " → ");
    }

    /** 从「片段1，片段2，…」中取出以 {@code fieldPrefix} 开头的片段值（去掉前缀） */
    private static String extractCommaField(String m, String fieldPrefix) {
        if (!StringUtils.hasText(m) || !StringUtils.hasText(fieldPrefix)) {
            return null;
        }
        for (String part : m.split("，")) {
            String p = part.trim();
            if (p.startsWith(fieldPrefix)) {
                String v = p.substring(fieldPrefix.length()).trim();
                return v.length() > 0 ? v : null;
            }
        }
        return null;
    }

    private static String substringBetweenPrefixes(String m, String a, String b) {
        int ia = m.indexOf(a);
        if (ia < 0) {
            return null;
        }
        int ib = m.indexOf(b, ia + a.length());
        if (ib < 0) {
            return null;
        }
        return m.substring(ia + a.length(), ib);
    }

    private static String substringAfterPrefix(String m, String pref) {
        int i = m.indexOf(pref);
        if (i < 0) {
            return null;
        }
        return m.substring(i + pref.length()).trim();
    }

    /**
     * 逗号或 | 分隔的 UserID → 企微 API 要求的 user1|user2
     */
    private static String buildTouserParam(String raw) {
        if (!StringUtils.hasText(raw)) {
            return null;
        }
        String[] parts = raw.split("[,|]");
        List<String> ids = new ArrayList<>();
        for (String p : parts) {
            String t = p == null ? "" : p.trim();
            if (t.length() > 0) {
                ids.add(t);
            }
        }
        if (ids.isEmpty()) {
            return null;
        }
        return String.join("|", ids);
    }

    private static String normalizeBase(String base) {
        String b = base.trim();
        if (b.endsWith("/")) {
            return b.substring(0, b.length() - 1);
        }
        return b;
    }

    /**
     * 服务监控展示：闲鱼企微应用推送接口完整 URL（不发起请求）
     */
    public String getGoofishPushEndpointDisplay() {
        if (!StringUtils.hasText(wxsendBaseUrl)) {
            return "";
        }
        return normalizeBase(wxsendBaseUrl) + "/wx/send/goofish";
    }

    /**
     * 服务监控手动测试：经 wxSend 向企微「闲鱼」应用发一条文本。
     *
     * @return null 表示 HTTP 2xx 且 wxSend 返回 code=200；非空为可直接展示的失败原因
     */
    public String testGoofishNotify() {
        if (!StringUtils.hasText(wxsendBaseUrl)) {
            return "未配置 jarvis.wecom.wxsend-base-url";
        }
        if (!StringUtils.hasText(wxsendVanToken)) {
            return "未配置 jarvis.wecom.wxsend-van-token（须与 wxSend TokenUtil 一致）";
        }
        if (!StringUtils.hasText(goofishNotifyTouser)) {
            return "未配置 jarvis.wecom.goofish-notify-touser（接收人为空）";
        }
        String content = "【服务监控·闲鱼通知测试】RuoYi 手动触发 " + new java.util.Date();
        boolean ok = pushGoofishAgentText(null, "", content);
        if (ok) {
            return null;
        }
        return "推送未成功：请核对 wxSend 服务、vanToken、企微闲鱼应用及接收人，或查看服务端日志";
    }

    /**
     * POST {@code /wx/send/goofish}：body 与 {@code /wx/send/pdd} 相同字段名（title、text、touser）。
     */
    private boolean postMessageReturnsOk(String url, String vanToken, String title, String text, String touserPipeJoined)
        throws Exception {
        JSONObject body = new JSONObject();
        body.put("title", title != null ? title : "");
        body.put("text", text != null ? text : "");
        body.put("touser", touserPipeJoined);
        byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8);
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) new URL(url).openConnection();
            conn.setRequestMethod("POST");
            conn.setConnectTimeout(15000);
            conn.setReadTimeout(60000);
            conn.setDoOutput(true);
            conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            conn.setRequestProperty("vanToken", vanToken);
            try (OutputStream os = conn.getOutputStream()) {
                os.write(bytes);
            }
            int httpCode = conn.getResponseCode();
            InputStream is = httpCode >= 200 && httpCode < 300 ? conn.getInputStream() : conn.getErrorStream();
            String resp = readAll(is);
            if (httpCode < 200 || httpCode >= 300) {
                log.warn("wxSend /wx/send/goofish HTTP {} body={}", httpCode, resp);
                return false;
            }
            Integer bizCode = null;
            try {
                JSONObject jo = JSONObject.parseObject(resp);
                if (jo != null) {
                    bizCode = jo.getInteger("code");
                }
            } catch (Exception parseEx) {
                log.debug("解析 wxSend 响应: {}", parseEx.toString());
            }
            if (bizCode != null && bizCode == 200) {
                log.debug("wxSend /wx/send/goofish OK resp={}", resp);
                return true;
            }
            log.warn("wxSend /wx/send/goofish 业务未成功 resp={}", resp);
            return false;
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }

    private static String readAll(InputStream is) throws java.io.IOException {
        if (is == null) {
            return "";
        }
        byte[] buf = new byte[4096];
        StringBuilder sb = new StringBuilder();
        int n;
        while ((n = is.read(buf)) >= 0) {
            sb.append(new String(buf, 0, n, StandardCharsets.UTF_8));
        }
        return sb.toString();
    }
}
