package com.erp.logistics.job;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.erp.logistics.entity.WaybillStatus;
import com.erp.logistics.mapper.WaybillStatusMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

/**
 * 物流异常检测定时任务
 *
 * 检测以下三类异常：
 * 1. 运单状态为 EXCEPTION（物流商报告异常）
 * 2. 同步失败次数超过阈值（sync_status=3 且重试超限）
 * 3. 运单停滞超过指定小时数无新轨迹（未签收且未退回）
 *
 * 触发时间：每小时第5分钟（可配置）
 * 告警去重：同一运单异常告警24小时内不重复发送
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class LogisticsExceptionDetectJob {

    private final WaybillStatusMapper waybillStatusMapper;
    private final RedisTemplate<String, Object> redisTemplate;

    private static final String ALERT_DEDUP_KEY_PREFIX = "logistics:alert:exception:";

    @Value("${logistics.exception.stale-hours:48}")
    private Integer staleHours;

    @Value("${logistics.exception.max-retry:5}")
    private Integer maxRetry;

    @Value("${logistics.exception.alert-dedup-hours:24}")
    private Integer alertDedupHours;

    /**
     * 每小时检测一次（每小时第5分钟执行）
     * 也可通过 scheduled-task-service 的 Quartz 触发
     */
    @Scheduled(cron = "${logistics.exception.detect-cron:0 5 * * * ?}")
    public void detectExceptions() {
        log.info("[异常检测] ========== 开始物流异常检测 ==========");
        long startTime = System.currentTimeMillis();

        try {
            int total = detectAbnormalStatus()
                    + detectSyncFailures()
                    + detectStaleWaybills()
                    + detectDeliveryTimeout();
            log.info("[异常检测] ========== 检测完成，合计告警: {} 条，耗时: {}ms",
                    total, System.currentTimeMillis() - startTime);
        } catch (Exception e) {
            log.error("[异常检测] 异常检测任务执行异常", e);
        }
    }

    // ==================== 检测方法 ====================

    /**
     * 1. 检测异常状态运单（status = EXCEPTION）
     */
    private int detectAbnormalStatus() {
        LambdaQueryWrapper<WaybillStatus> wrapper = new LambdaQueryWrapper<WaybillStatus>()
                .eq(WaybillStatus::getStatus, "EXCEPTION")
                .eq(WaybillStatus::getDeleted, 0);

        List<WaybillStatus> exceptions = waybillStatusMapper.selectList(wrapper);

        int alerted = 0;
        for (WaybillStatus waybill : exceptions) {
            if (shouldAlert(waybill.getWaybillNo(), "EXCEPTION")) {
                log.warn("[异常检测] 🚨 运单异常 | waybillNo={} | carrier={} | status={} | location={} | description={}",
                        waybill.getWaybillNo(), waybill.getCarrier(),
                        waybill.getStatusLabel(), waybill.getLocation(), waybill.getDescription());
                sendAlert(waybill, "EXCEPTION", "运单状态异常");
                alerted++;
            }
        }

        if (!exceptions.isEmpty()) {
            log.info("[异常检测] 发现 {} 条异常状态运单（其中 {} 条已发送告警）",
                    exceptions.size(), alerted);
        }
        return alerted;
    }

    /**
     * 2. 检测同步失败运单（sync_status = 3 且重试次数超限）
     */
    private int detectSyncFailures() {
        LambdaQueryWrapper<WaybillStatus> wrapper = new LambdaQueryWrapper<WaybillStatus>()
                .eq(WaybillStatus::getSyncStatus, 3)
                .ge(WaybillStatus::getSyncRetryCount, maxRetry)
                .eq(WaybillStatus::getDeleted, 0);

        List<WaybillStatus> failures = waybillStatusMapper.selectList(wrapper);

        int alerted = 0;
        for (WaybillStatus waybill : failures) {
            if (shouldAlert(waybill.getWaybillNo(), "SYNC_FAIL")) {
                log.error("[异常检测] ❌ 同步失败 | waybillNo={} | carrier={} | retryCount={} | reason={}",
                        waybill.getWaybillNo(), waybill.getCarrier(),
                        waybill.getSyncRetryCount(), waybill.getSyncFailReason());
                sendAlert(waybill, "SYNC_FAIL", "轨迹同步失败");
                alerted++;
            }
        }

        if (!failures.isEmpty()) {
            log.info("[异常检测] 发现 {} 条同步失败运单（其中 {} 条已发送告警）",
                    failures.size(), alerted);
        }
        return alerted;
    }

    /**
     * 3. 检测停滞运单（超过 staleHours 小时无新轨迹且未签收/退回）
     */
    private int detectStaleWaybills() {
        LocalDateTime threshold = LocalDateTime.now().minusHours(staleHours);

        LambdaQueryWrapper<WaybillStatus> wrapper = new LambdaQueryWrapper<WaybillStatus>()
                .notIn(WaybillStatus::getStatus, "SIGNED", "RETURNED", "RETURNING")
                .eq(WaybillStatus::getDeleted, 0)
                .and(w -> w
                        .isNull(WaybillStatus::getLastTraceTime)
                        .or()
                        .lt(WaybillStatus::getLastTraceTime, threshold)
                );

        List<WaybillStatus> stale = waybillStatusMapper.selectList(wrapper);

        int alerted = 0;
        for (WaybillStatus waybill : stale) {
            long hours = waybill.getLastTraceTime() == null
                    ? Duration.between(waybill.getCreatedAt(), LocalDateTime.now()).toHours()
                    : Duration.between(waybill.getLastTraceTime(), LocalDateTime.now()).toHours();

            if (shouldAlert(waybill.getWaybillNo(), "STALE")) {
                log.warn("[异常检测] ⚠️ 运单停滞 | waybillNo={} | carrier={} | lastTraceTime={} | 停滞={}小时 | status={}",
                        waybill.getWaybillNo(), waybill.getCarrier(),
                        waybill.getLastTraceTime(), hours, waybill.getStatus());
                sendAlert(waybill, "STALE", "运单停滞超过" + staleHours + "小时");
                alerted++;
            }
        }

        if (!stale.isEmpty()) {
            log.info("[异常检测] 发现 {} 条停滞运单（超过{}小时无更新，其中 {} 条已发送告警）",
                    stale.size(), staleHours, alerted);
        }
        return alerted;
    }

    /**
     * 4. 检测派送超时（DELIVERING状态超过24小时）
     */
    private int detectDeliveryTimeout() {
        LocalDateTime threshold = LocalDateTime.now().minusHours(24);

        LambdaQueryWrapper<WaybillStatus> wrapper = new LambdaQueryWrapper<WaybillStatus>()
                .eq(WaybillStatus::getStatus, "DELIVERING")
                .eq(WaybillStatus::getDeleted, 0)
                .lt(WaybillStatus::getLastTraceTime, threshold);

        List<WaybillStatus> timeouts = waybillStatusMapper.selectList(wrapper);

        int alerted = 0;
        for (WaybillStatus waybill : timeouts) {
            if (shouldAlert(waybill.getWaybillNo(), "DELIVERY_TIMEOUT")) {
                long hours = Duration.between(waybill.getLastTraceTime(), LocalDateTime.now()).toHours();
                log.warn("[异常检测] ⏰ 派送超时 | waybillNo={} | carrier={} | 派送中={}小时 | 收件人={} {}",
                        waybill.getWaybillNo(), waybill.getCarrier(), hours,
                        waybill.getReceiverName(), waybill.getReceiverPhone());
                sendAlert(waybill, "DELIVERY_TIMEOUT", "派送超时超过24小时");
                alerted++;
            }
        }

        if (!timeouts.isEmpty()) {
            log.info("[异常检测] 发现 {} 条派送超时运单（其中 {} 条已发送告警）",
                    timeouts.size(), alerted);
        }
        return alerted;
    }

    // ==================== 告警去重 & 发送 ====================

    /**
     * 告警去重：24小时内同一运单同一类型不重复告警
     */
    private boolean shouldAlert(String waybillNo, String alertType) {
        String key = ALERT_DEDUP_KEY_PREFIX + waybillNo + ":" + alertType;
        Boolean exists = redisTemplate.hasKey(key);
        if (Boolean.TRUE.equals(exists)) {
            return false;  // 近期已告警，跳过
        }
        // 设置去重Key，24小时后自动过期
        redisTemplate.opsForValue().set(key, "1", Duration.ofHours(alertDedupHours));
        return true;
    }

    /**
     * 发送告警通知（TODO: 接入实际告警渠道）
     * 当前实现：打印日志，可扩展为钉钉/飞书/邮件/SMS
     */
    private void sendAlert(WaybillStatus waybill, String alertType, String alertMsg) {
        String message = String.format(
                "【物流异常告警】\n" +
                "类型: %s\n" +
                "运单号: %s\n" +
                "物流商: %s\n" +
                "当前状态: %s\n" +
                "位置: %s\n" +
                "描述: %s\n" +
                "收件人: %s %s\n" +
                "告警时间: %s",
                alertMsg,
                waybill.getWaybillNo(),
                waybill.getCarrier(),
                waybill.getStatusLabel(),
                waybill.getLocation(),
                waybill.getDescription(),
                waybill.getReceiverName(),
                waybill.getReceiverPhone(),
                LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
        );

        // TODO: 实际告警渠道扩展点
        // 1. 钉钉机器人: DingTalkNotifier
        // 2. 飞书机器人: LarkNotifier
        // 3. 邮件: EmailAlertService
        // 4. 短信: SmsAlertService
        // 5. 系统通知: NotificationService

        log.warn("[告警] {}", message);
    }

    // ==================== 手动触发（供 Quartz 调用） ====================

    /**
     * 手动执行一次检测（供 scheduled-task-service 外部触发）
     */
    public void executeManual() {
        log.info("[异常检测] 手动触发开始");
        detectExceptions();
    }
}
