package com.ruoyi.jarvis.service.impl;

import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import com.ruoyi.jarvis.domain.TgScalperPhone;
import com.ruoyi.jarvis.domain.dto.WeComInboundRequest;
import com.ruoyi.jarvis.service.IPhoneForwardActivePush;
import com.ruoyi.jarvis.service.ITgScalperPhoneService;
import com.ruoyi.jarvis.service.SuperAdminService;
import com.ruoyi.jarvis.util.WeComUtf8ChunkUtil;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 企微「开」/「慢开」+ 手机号：POST 局域网 /v1/forward，body 含 {@code text}（手机号）与 {@code bot}，
 * 将 JSON 中的 {@code reply_text} 作为回显。
 * <p>
 * {@code wait_reply} 时：{@code AJL05_bot} 固定取第 2 条；{@code QingBaoJuXWsgkbot} 由 tg_bridge
 * 在同一会话内多次收取（仅一次发送 query），按第 2 条是否已为结果决定在 2/3 条间取值，避免重复计费。
 * 「慢开」返回仍会去掉尾部固定推广行。
 * </p>
 * <p>
 * <b>计费幂等</b>：与 {@link java.util.concurrent.locks.ReentrantLock} 只能串行化并发、不能防止企微对同一 {@code MsgId} 的重试。
 * 开启 {@code jarvis.phone-forward.dedup-enabled} 后，对同一 msgId（wxSend 已传入）在 TTL 内复用首次成功结果，
 * 并在获取锁后再次查缓存，避免「上一请求已付费返回、排队中的重试」再次调用 Telegram。
 * 开启 {@code jarvis.phone-forward.async-result-push-enabled} 时，被动立即回复「收到电话：…」，TG 结果经 wxSend 主动推送。
 * </p>
 */
@Service
public class OpenPhoneForwardService {

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

    private static final Pattern MOBILE_11 = Pattern.compile("(1\\d{10})");

    /** 「开」→ 对应 Bot 用户名（与对端 {@code bot} 字段一致，不含 @） */
    private static final String BOT_OPEN = "AJL05_bot";

    /** 「慢开」→ 对应 Bot 用户名 */
    private static final String BOT_SLOW_OPEN = "QingBaoZhuQuebot";

    private static final int REPLY_TAKE_NTH_OPEN_BOT = 2;

    /**
     * 情报局推广条：Telegram 常为 Markdown
     * {@code **👉‍**[**文案**](url)**👈**}、{@code 👉‍[文案](url)👈}；另保留旧版纯文本。
     */
    private static final Pattern[] QINGBAO_REPLY_JUNK_PATTERNS = {
        Pattern.compile(
            "\\*\\*👉\u200D?\\*\\*\\[\\*\\*公安路线查询价格表\\*\\*\\]"
                + "\\(https?://t\\.me/\\+C20ADPmEKJU0ZGFl\\)\\*\\*👈\\*\\*"),
        Pattern.compile(
            "\\*\\*👉\u200D?\\*\\*\\[\\*\\*如机器人提示被注销点我防丢\\*\\*\\]"
                + "\\(https?://telegra\\.ph/qingbaoju-10-01\\)\\*\\*👈\\*\\*"),
        Pattern.compile(
            "\uD83D\uDC49\u200D?\\[公安路线查询价格表\\]"
                + "\\(https?://t\\.me/\\+C20ADPmEKJU0ZGFl\\)\uD83D\uDC48"),
        Pattern.compile(
            "\uD83D\uDC49\u200D?\\[如机器人提示被注销点我防丢\\]"
                + "\\(https?://telegra\\.ph/qingbaoju-10-01\\)\uD83D\uDC48"),
    };

    private static final String[] QINGBAO_REPLY_JUNK_LITERAL = {
        "\uD83D\uDC49\u200D公安路线查询价格表 (https://t.me/+C20ADPmEKJU0ZGFl)\uD83D\uDC48",
        "\uD83D\uDC49\u200D如机器人提示被注销点我防丢 (https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48",
        "\uD83D\uDC49公安路线查询价格表 (https://t.me/+C20ADPmEKJU0ZGFl)\uD83D\uDC48",
        "\uD83D\uDC49如机器人提示被注销点我防丢 (https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48",
        "\uD83D\uDC49\u200D[公安路线查询价格表](https://t.me/+C20ADPmEKJU0ZGFl)\uD83D\uDC48",
        "\uD83D\uDC49\u200D[如机器人提示被注销点我防丢](https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48",
        "\uD83D\uDC49[公安路线查询价格表](https://t.me/+C20ADPmEKJU0ZGFl)\uD83D\uDC48",
        "\uD83D\uDC49[如机器人提示被注销点我防丢](https://telegra.ph/qingbaoju-10-01)\uD83D\uDC48",
    };

    @Value("${jarvis.phone-forward.enabled:false}")
    private boolean enabled;

    @Value("${jarvis.phone-forward.base-url:}")
    private String baseUrl;

    @Value("${jarvis.phone-forward.path:/v1/forward}")
    private String path;

    @Value("${jarvis.phone-forward.connect-timeout-ms:8000}")
    private int connectTimeoutMs;

    @Value("${jarvis.phone-forward.read-timeout-ms:60000}")
    private int readTimeoutMs;

    /** 为 true 时在 POST body 中携带 wait_reply、reply_take_nth（单次请求生效） */
    @Value("${jarvis.phone-forward.wait-reply:true}")
    private boolean waitReply;

    /** 与 tg_bridge 串行：多线程同时「开」时排队，避免 Python 端会话串话；0 表示无限等待 */
    @Value("${jarvis.phone-forward.lock-acquire-timeout-ms:180000}")
    private long lockAcquireTimeoutMs;

    /** 连续失败达到阈值后，在 openDurationMs 内直接拒绝调用（不发起 HTTP） */
    @Value("${jarvis.phone-forward.circuit-failure-threshold:5}")
    private int circuitFailureThreshold;

    @Value("${jarvis.phone-forward.circuit-open-ms:120000}")
    private long circuitOpenMs;

    /** 为 true 时先查黄牛电话库，命中且备注非空则不再请求 Telegram */
    @Value("${jarvis.phone-forward.scalper-library-enabled:true}")
    private boolean scalperLibraryEnabled;

    /**
     * 同一企微 MsgId 在 TTL 内只走一次付费转发（成功结果缓存；错误文案不缓存以便用户重试）
     */
    @Value("${jarvis.phone-forward.dedup-enabled:true}")
    private boolean dedupEnabled;

    @Value("${jarvis.phone-forward.dedup-ttl-seconds:900}")
    private int dedupTtlSeconds;

    /**
     * 为 true 时：立即被动回复「收到电话」，TG 在后台查询并通过 wxSend 主动推送全文
     */
    @Value("${jarvis.phone-forward.async-result-push-enabled:true}")
    private boolean asyncResultPushEnabled;

    @Resource
    private ITgScalperPhoneService tgScalperPhoneService;

    @Resource
    private SuperAdminService superAdminService;

    @Autowired(required = false)
    private IPhoneForwardActivePush phoneForwardActivePush;

    /** 仅允许单飞：所有 phone-forward 请求串行 */
    private final ReentrantLock tgBridgeCallLock = new ReentrantLock(true);

    /** 异步 TG 查询进行中（与幂等 dedup 使用同一关联键） */
    private final ConcurrentHashMap<String, Long> forwardInflightUntilMs = new ConcurrentHashMap<>();

    private static final class DedupEntry {
        final String reply;
        final long expireAtMs;

        DedupEntry(String reply, long expireAtMs) {
            this.reply = reply;
            this.expireAtMs = expireAtMs;
        }
    }

    /** key → 已成功返回给用户的正文（含黄牛库与 TG） */
    private final ConcurrentHashMap<String, DedupEntry> forwardReplyDedup = new ConcurrentHashMap<>();

    private final AtomicInteger circuitFailureCount = new AtomicInteger(0);

    /** 熔断恢复时间（epoch ms），0 表示未熔断 */
    private final AtomicLong circuitOpenUntilMs = new AtomicLong(0);
    /**
     * @param req 须含 content；msgId 由 wxSend 透传时可用于幂等防重复计费
     * @return 非 null 表示本条消息已由本服务处理（含错误提示）；null 表示不匹配规则
     */
    public String tryReply(WeComInboundRequest req) {
        if (req == null) {
            return null;
        }
        String rawContent = req.getContent();
        if (!enabled || !StringUtils.hasText(baseUrl) || rawContent == null) {
            return null;
        }
        String text = rawContent.trim().replaceFirst("^\uFEFF", "");
        String bot;
        if (text.startsWith("慢开")) {
            bot = BOT_SLOW_OPEN;
        } else if (text.startsWith("开")) {
            bot = BOT_OPEN;
        } else {
            return null;
        }
        String phone = extractFirstMobile11(text);
        if (phone == null) {
            return null;
        }
        final String cKey = resolveCorrelationKey(req, text, phone, bot);

        if (dedupEnabled) {
            String cached = dedupGet(cKey);
            if (cached != null) {
                log.info("phone-forward 幂等命中 key={} phone={} bot={}", cKey, phone, bot);
                if (asyncResultPushEnabled) {
                    return "该消息已查询过，结果已通过应用消息推送，请在会话中查看。";
                }
                return cached;
            }
        }

        if (asyncResultPushEnabled && isForwardInflight(cKey)) {
            return "查询进行中，请稍候查看应用消息。";
        }

        if (scalperLibraryEnabled) {
            TgScalperPhone hit = tgScalperPhoneService.selectEnabledByPhone(phone);
            if (hit != null && StringUtils.hasText(hit.getRemark())) {
                String remark = hit.getRemark().trim();
                log.info("phone-forward 黄牛电话库命中 phone={}，跳过 Telegram", phone);
                dedupPut(cKey, remark);
                return remark;
            }
        }

        if (asyncResultPushEnabled && phoneForwardActivePush != null && StringUtils.hasText(req.getFromUserName())) {
            markForwardInflight(cKey);
            final String pushTouserPipe = superAdminService.resolveTouserPipeForActivePush(req.getFromUserName());
            final String callbackAgentId = StringUtils.hasText(req.getAgentId()) ? req.getAgentId().trim() : null;
            CompletableFuture.runAsync(() -> runForwardAndPush(pushTouserPipe, callbackAgentId, phone, bot, cKey));
            return String.format("收到电话：%s。\n后续结果将通过应用消息推送。", phone);
        }

        return doForward(phone, bot, dedupEnabled ? cKey : null);
    }

    private static String resolveCorrelationKey(WeComInboundRequest req, String text, String phone, String bot) {
        String msgId = req.getMsgId() != null ? req.getMsgId().trim() : "";
        if (StringUtils.hasText(msgId)) {
            return "pf:msg:" + msgId;
        }
        String from = req.getFromUserName() != null ? req.getFromUserName().trim() : "";
        return "pf:h:" + from + ":" + Integer.toHexString(Objects.hash(text, phone, bot));
    }

    private boolean isForwardInflight(String cKey) {
        Long until = forwardInflightUntilMs.get(cKey);
        if (until == null) {
            return false;
        }
        if (System.currentTimeMillis() > until) {
            forwardInflightUntilMs.remove(cKey, until);
            return false;
        }
        return true;
    }

    private void markForwardInflight(String cKey) {
        long until = System.currentTimeMillis() + Math.max(120_000L, (long) dedupTtlSeconds * 1000L);
        forwardInflightUntilMs.put(cKey, until);
    }

    private void clearForwardInflight(String cKey) {
        forwardInflightUntilMs.remove(cKey);
    }

    private void runForwardAndPush(String pushTouserPipe, String wecomCallbackAgentId, String phone, String bot, String cKey) {
        try {
            log.info(
                "phone-forward 异步 TG 查询开始 phone={} bot={} touser={} callbackAgentId={}",
                phone, bot, pushTouserPipe, wecomCallbackAgentId);
            String reply = doForward(phone, bot, dedupEnabled ? cKey : null);
            List<String> chunks = WeComUtf8ChunkUtil.splitUtf8Chunks(reply, WeComUtf8ChunkUtil.WE_COM_TEXT_MAX_UTF8_BYTES);
            chunks.removeIf(s -> !StringUtils.hasText(s));
            if (chunks.isEmpty()) {
                chunks = Collections.singletonList("（无返回内容）");
            }
            boolean pushed = phoneForwardActivePush.schedulePushChunks(pushTouserPipe, wecomCallbackAgentId, chunks);
            if (!pushed) {
                log.error(
                    "phone-forward 结果未能推送到企微 touser={} phone={}（可能为 TG/转发错误或服务端返回的正文，请检查 wxSend 与 jarvis.wecom）",
                    pushTouserPipe, phone);
            }
        } catch (Exception e) {
            log.error("phone-forward 异步处理异常 phone={} user={}", phone, pushTouserPipe, e);
            pushForwardFailureNotice(pushTouserPipe, wecomCallbackAgentId, userVisibleThrowableMessage(e));
        } finally {
            clearForwardInflight(cKey);
        }
    }

    private void pushForwardFailureNotice(String pushTouserPipe, String wecomCallbackAgentId, String line) {
        if (phoneForwardActivePush == null || !StringUtils.hasText(pushTouserPipe)) {
            return;
        }
        try {
            List<String> parts = WeComUtf8ChunkUtil.splitUtf8Chunks(line, WeComUtf8ChunkUtil.WE_COM_TEXT_MAX_UTF8_BYTES);
            if (parts.isEmpty()) {
                parts = Collections.singletonList("「转发服务」异常，请稍后重试。");
            }
            boolean ok = phoneForwardActivePush.schedulePushChunks(pushTouserPipe.trim(), wecomCallbackAgentId, parts);
            if (!ok) {
                log.error("phone-forward 异常说明未能推送到企微 user={}", pushTouserPipe);
            }
        } catch (Exception e2) {
            log.warn("phone-forward 异常说明推送过程失败 err={}", e2.toString());
        }
    }

    private static String userVisibleThrowableMessage(Throwable e) {
        String m = e.getMessage();
        if (!StringUtils.hasText(m)) {
            m = e.getClass().getSimpleName();
        }
        if (m.length() > 500) {
            m = m.substring(0, 500) + "…";
        }
        return "「转发服务」异常：" + m + "。请稍后重试。";
    }

    private String dedupGet(String key) {
        if (!dedupEnabled || key == null) {
            return null;
        }
        DedupEntry e = forwardReplyDedup.get(key);
        if (e == null) {
            return null;
        }
        if (System.currentTimeMillis() > e.expireAtMs) {
            forwardReplyDedup.remove(key, e);
            return null;
        }
        return e.reply;
    }

    private void dedupPut(String key, String reply) {
        if (key == null || !dedupEnabled || !StringUtils.hasText(reply) || !shouldCacheReplyForDedup(reply)) {
            return;
        }
        long ttlMs = Math.max(60_000L, (long) dedupTtlSeconds * 1000L);
        forwardReplyDedup.put(key, new DedupEntry(reply, System.currentTimeMillis() + ttlMs));
    }

    /** 失败提示不缓存，避免用户无法通过重试恢复 */
    private static boolean shouldCacheReplyForDedup(String reply) {
        return !reply.startsWith("「转发服务」");
    }

    private boolean isCircuitOpen() {
        long until = circuitOpenUntilMs.get();
        return until > 0L && System.currentTimeMillis() < until;
    }

    private void recordSuccess() {
        circuitFailureCount.set(0);
        circuitOpenUntilMs.set(0L);
    }

    /** HTTP/网络类失败、5xx、超时记入；达到阈值则熔断一段时间 */
    private void recordFailure() {
        long now = System.currentTimeMillis();
        long until = circuitOpenUntilMs.get();
        if (until > now) {
            return;
        }
        int n = circuitFailureCount.incrementAndGet();
        int thr = Math.max(1, circuitFailureThreshold);
        if (n >= thr) {
            long openUntil = now + Math.max(1000L, circuitOpenMs);
            circuitOpenUntilMs.set(openUntil);
            circuitFailureCount.set(0);
            log.warn("phone-forward 熔断开启至 epochMs={}（连续失败 ≥ {}）", openUntil, thr);
        }
    }

    private static boolean shouldTripCircuit(int httpCode) {
        return httpCode == 504 || httpCode >= 500;
    }

    private static String extractFirstMobile11(String text) {
        Matcher m = MOBILE_11.matcher(text);
        if (m.find()) {
            return m.group(1);
        }
        return null;
    }

    /**
     * @param dedupKey 非空且开启幂等时：持锁后再次查缓存，避免首请求已付费、重试排队后二次请求 Telegram
     */
    private String doForward(String phone, String bot, String dedupKey) {
        if (isCircuitOpen()) {
            log.warn("phone-forward 熔断拒绝 phone={} bot={}", phone, bot);
            return "「转发服务」暂时不可用（连续失败保护中），请一分钟后再试。";
        }

        boolean locked = false;
        try {
            if (lockAcquireTimeoutMs <= 0) {
                tgBridgeCallLock.lock();
                locked = true;
            } else {
                locked = tgBridgeCallLock.tryLock(lockAcquireTimeoutMs, TimeUnit.MILLISECONDS);
                if (!locked) {
                    log.warn(
                        "phone-forward 排队超时（上一条未完成）phone={} bot={} ms={}",
                        phone, bot, lockAcquireTimeoutMs);
                    return "「转发服务」正忙（上一条查询尚未结束），请稍后再试。";
                }
            }
            if (dedupKey != null) {
                String again = dedupGet(dedupKey);
                if (again != null) {
                    log.info("phone-forward 持锁后幂等命中，跳过本次 HTTP/Telegram phone={} bot={}", phone, bot);
                    return again;
                }
            }
            String reply = doForwardUnsynchronized(phone, bot);
            dedupPut(dedupKey, reply);
            return reply;
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            log.warn("phone-forward 获取锁被打断 phone={} bot={}", phone, bot);
            return "「转发服务」被中断，请稍后再试。";
        } finally {
            if (locked) {
                tgBridgeCallLock.unlock();
            }
        }
    }

    private String doForwardUnsynchronized(String phone, String bot) {
        try {
            String base = baseUrl.trim();
            if (base.endsWith("/")) {
                base = base.substring(0, base.length() - 1);
            }
            String p = path.startsWith("/") ? path : "/" + path;
            String q;
            try {
                q = URLEncoder.encode(phone, StandardCharsets.UTF_8.name());
            } catch (UnsupportedEncodingException e) {
                q = phone;
            }
            String urlStr = base + p + "?text=" + q;

            JSONObject body = new JSONObject();
            body.put("text", phone);
            body.put("bot", bot);
            if (waitReply) {
                body.put("wait_reply", true);
                if (BOT_SLOW_OPEN.equals(bot)) {
                    body.put("reply_adaptive_skip_middle_ad", true);
                } else {
                    body.put("reply_take_nth", REPLY_TAKE_NTH_OPEN_BOT);
                }
            }
            byte[] bytes = body.toJSONString().getBytes(StandardCharsets.UTF_8);

            HttpURLConnection conn = null;
            try {
                conn = (HttpURLConnection) new URL(urlStr).openConnection();
                conn.setRequestMethod("POST");
                conn.setConnectTimeout(connectTimeoutMs);
                conn.setReadTimeout(readTimeoutMs);
                conn.setDoOutput(true);
                conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
                conn.setRequestProperty("Accept", "*/*");
                try (OutputStream os = conn.getOutputStream()) {
                    os.write(bytes);
                }
                int code = conn.getResponseCode();
                InputStream is = code >= 200 && code < 300 ? conn.getInputStream() : conn.getErrorStream();
                String resp = readAll(is);
                if (code < 200 || code >= 300) {
                    log.warn("phone-forward HTTP {} url={} body={}", code, urlStr, resp);
                    if (shouldTripCircuit(code)) {
                        recordFailure();
                    }
                    return "「转发服务」请求失败（HTTP " + code + "），请稍后再试。";
                }
                JSONObject jo = JSONObject.parseObject(resp);
                if (jo == null) {
                    recordFailure();
                    return "「转发服务」返回异常，请稍后再试。";
                }
                String reply = jo.getString("reply_text");
                if (!StringUtils.hasText(reply)) {
                    recordFailure();
                    return "「转发服务」未返回 reply_text。";
                }
                if (BOT_SLOW_OPEN.equals(bot)) {
                    reply = filterQingBaoAdLines(reply);
                }
                reply = stripReplyAsterisks(reply);
                recordSuccess();
                return reply;
            } finally {
                if (conn != null) {
                    conn.disconnect();
                }
            }
        } catch (SocketTimeoutException e) {
            log.warn("phone-forward 超时 phone={} bot={} err={}", phone, bot, e.toString());
            recordFailure();
            return "「转发服务」超时，请稍后再试。";
        } catch (Exception e) {
            log.warn("phone-forward 异常 phone={} bot={} err={}", phone, bot, e.toString());
            recordFailure();
            return "「转发服务」连接失败，请确认 Jarvis 与局域网服务可达。";
        }
    }

    /** 去掉 Telegram 回复里常用 Markdown 星号，便于企微纯文本展示 */
    private static String stripReplyAsterisks(String reply) {
        if (reply == null) {
            return null;
        }
        return reply.replace("*", "");
    }

    /** 将固定广告段（Markdown 与纯文本）替换为空格后 trim。 */
    private static String filterQingBaoAdLines(String reply) {
        String s = reply;
        for (Pattern p : QINGBAO_REPLY_JUNK_PATTERNS) {
            s = p.matcher(s).replaceAll(" ");
        }
        for (String junk : QINGBAO_REPLY_JUNK_LITERAL) {
            s = s.replace(junk, " ");
        }
        return s.trim();
    }

    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();
    }
}
