package com.kym.service.impl;

import com.kym.common.enums.FaultType;
import com.kym.common.enums.MsgTemplateType;
import com.kym.common.utils.CommUtil;
import com.kym.common.utils.wx.WxPbUtil;
import com.kym.entity.FaultRecord;
import com.kym.entity.MpMsgTemplate;
import com.kym.entity.common.RedisKeys;
import com.kym.service.FaultNotificationService;
import com.kym.service.FaultRecordService;
import com.kym.service.FaultSubscriberService;
import com.kym.service.MpMsgTemplateService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;

/**
 * 故障通知核心服务实现
 *
 * @author skyline
 */
@Slf4j
@Service
public class FaultNotificationServiceImpl implements FaultNotificationService {

    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final Duration ANTI_DUP_TTL = Duration.ofHours(2);

    @Value("${fault.notify-delay:3}")
    private int faultNotifyDelayMinutes;

    private final FaultRecordService faultRecordService;
    private final FaultSubscriberService faultSubscriberService;
    private final MpMsgTemplateService mpMsgTemplateService;
    private final StringRedisTemplate stringRedisTemplate;

    public FaultNotificationServiceImpl(FaultRecordService faultRecordService,
                                         FaultSubscriberService faultSubscriberService,
                                         MpMsgTemplateService mpMsgTemplateService,
                                         StringRedisTemplate stringRedisTemplate) {
        this.faultRecordService = faultRecordService;
        this.faultSubscriberService = faultSubscriberService;
        this.mpMsgTemplateService = mpMsgTemplateService;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public void handleOfflineFault(String stationId, String deviceName) {
        handleFault(stationId, deviceName, FaultType.OFFLINE, null);
    }

    @Override
    public void handleOnlineRecovery(String stationId, String deviceName) {
        handleRecovery(stationId, deviceName, FaultType.OFFLINE);
    }

    @Override
    public void handleResourceShortage(String stationId, String deviceName, String faultType) {
        FaultType type = FaultType.fromCode(faultType);
        handleFault(stationId, deviceName, type, null);
    }

    @Override
    public void handleResourceRecovery(String stationId, String deviceName, String faultType) {
        FaultType type = FaultType.fromCode(faultType);
        handleRecovery(stationId, deviceName, type);
    }

    @Override
    public void reconcileUnnotifiedFaults() {
        var unnotifiedList = faultRecordService.lambdaQuery()
                .eq(FaultRecord::getIsNotified, FaultRecord.IS_NOTIFIED_未通知)
                .eq(FaultRecord::getIsRecovered, FaultRecord.IS_RECOVERED_未恢复)
                .list();

        var now = LocalDateTime.now();
        var delayThreshold = now.minusMinutes(faultNotifyDelayMinutes);

        for (var record : unnotifiedList) {
            try {
                // 延迟未到，暂不通知
                if (record.getFaultTime() != null && record.getFaultTime().isAfter(delayThreshold)) {
                    log.debug("故障通知延迟未到，跳过: recordId={}, faultTime={}, delayMinutes={}",
                            record.getId(), record.getFaultTime(), faultNotifyDelayMinutes);
                    continue;
                }

                var redisKey = buildAntiDupKey(record.getFaultType(), record.getDeviceName(), record.getStationId());
                if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(redisKey))) {
                    log.debug("反重key存在，跳过等待下次兜底: recordId={}", record.getId());
                } else {
                    doNotify(record);
                }
            } catch (Exception e) {
                log.error("兜底通知发送失败: recordId={}", record.getId(), e);
            }
        }
    }

    // ========== private ==========

    private void handleFault(String stationId, String deviceName, FaultType faultType, String faultReason) {
        var existing = faultRecordService.getUnrecovered(stationId, deviceName, faultType.getCode());
        if (existing != null) {
            log.debug("故障记录已存在，跳过: stationId={}, deviceName={}, faultType={}", stationId, deviceName, faultType.getCode());
            return;
        }

        var record = new FaultRecord()
                .setStationId(stationId)
                .setDeviceName(deviceName)
                .setFaultType(faultType.getCode())
                .setFaultTime(LocalDateTime.now())
                .setFaultReason(faultReason)
                .setIsNotified(FaultRecord.IS_NOTIFIED_未通知)
                .setIsRecovered(FaultRecord.IS_RECOVERED_未恢复);
        faultRecordService.save(record);

        log.info("故障已记录，延迟{}分钟后发送通知: stationId={}, deviceName={}, faultType={}",
                faultNotifyDelayMinutes, stationId, deviceName, faultType.getCode());
    }

    private void handleRecovery(String stationId, String deviceName, FaultType faultType) {
        var record = faultRecordService.getUnrecovered(stationId, deviceName, faultType.getCode());
        if (record == null) {
            log.debug("未找到未恢复的故障记录，清理可能残留的反重key: stationId={}, deviceName={}, faultType={}",
                    stationId, deviceName, faultType.getCode());
            stringRedisTemplate.delete(buildAntiDupKey(faultType.getCode(), deviceName, stationId));
            return;
        }

        boolean wasNotified = record.getIsNotified() != null && record.getIsNotified() == FaultRecord.IS_NOTIFIED_已通知;

        faultRecordService.markRecovered(record.getId());
        stringRedisTemplate.delete(buildAntiDupKey(faultType.getCode(), deviceName, stationId));

        if (wasNotified) {
            sendRecoveryNotification(record);
        } else {
            log.info("故障在通知延迟内自动恢复，跳过通知: stationId={}, deviceName={}, faultType={}",
                    stationId, deviceName, faultType.getCode());
        }
    }

    private void doNotify(FaultRecord record) {
        var subscribers = faultSubscriberService.getSubscribedOpenids(record.getStationId());
        if (subscribers.isEmpty()) {
            log.debug("站点 {} 无故障通知订阅者，跳过通知", record.getStationId());
            return;
        }

        var template = getMpMsgTemplate(MsgTemplateType.FAULT_ALERT);
        if (template == null) {
            log.warn("未找到故障提醒模版消息配置");
            return;
        }

        var params = buildFaultAlertParams(record);
        int successCount = 0;
        for (var openid : subscribers) {
            try {
                WxPbUtil.sendPublicTemplateMessage(openid, template.getTemplateId(), params, "", "");
                successCount++;
            } catch (Exception e) {
                log.error("发送故障提醒失败: openid={}, recordId={}", openid, record.getId(), e);
            }
        }

        if (successCount > 0) {
            faultRecordService.markNotified(record.getId());
            stringRedisTemplate.opsForValue().set(
                    buildAntiDupKey(record.getFaultType(), record.getDeviceName(), record.getStationId()),
                    "1", ANTI_DUP_TTL);
        } else {
            log.warn("故障提醒全部发送失败({}/{}), 保留未通知状态等待下次兜底: recordId={}",
                    successCount, subscribers.size(), record.getId());
        }
    }

    private void sendRecoveryNotification(FaultRecord record) {
        var subscribers = faultSubscriberService.getSubscribedOpenids(record.getStationId());
        if (subscribers.isEmpty()) {
            return;
        }

        var template = getMpMsgTemplate(MsgTemplateType.FAULT_RECOVER);
        if (template == null) {
            log.debug("未找到故障恢复提醒模版消息配置，跳过恢复通知");
            return;
        }

        var params = buildFaultRecoverParams(record);
        int successCount = 0;
        for (var openid : subscribers) {
            try {
                WxPbUtil.sendPublicTemplateMessage(openid, template.getTemplateId(), params, "", "");
                successCount++;
            } catch (Exception e) {
                log.error("发送故障恢复提醒失败: openid={}, recordId={}", openid, record.getId(), e);
            }
        }
        if (successCount == 0) {
            log.warn("故障恢复提醒全部发送失败({}/{}): recordId={}",
                    successCount, subscribers.size(), record.getId());
        }
    }

    private Map<String, String> buildFaultAlertParams(FaultRecord record) {
        return Map.of(
                "thing26", getStationName(record.getStationId()),
                "thing9", record.getDeviceName(),
                "thing5", FaultType.fromCode(record.getFaultType()).getDesc(),
                "time2", record.getFaultTime().format(FORMATTER)
        );
    }

    private Map<String, String> buildFaultRecoverParams(FaultRecord record) {
        return Map.of(
                "thing14", getStationName(record.getStationId()),
                "thing3", record.getDeviceName(),
                "thing2", FaultType.fromCode(record.getFaultType()).getDesc(),
                "time6", LocalDateTime.now().format(FORMATTER)
        );
    }

    private MpMsgTemplate getMpMsgTemplate(MsgTemplateType type) {
        return mpMsgTemplateService.lambdaQuery()
                .eq(MpMsgTemplate::getBizType, type.getBizType())
                .one();
    }

    private String buildAntiDupKey(String faultType, String deviceName, String stationId) {
        return RedisKeys.FAULT_NOTIFIED + faultType + ":" + deviceName + ":" + stationId;
    }

    private String getStationName(String stationId) {
        var name = com.kym.service.cache.KymCache.INSTANCE.getStationNameById(stationId);
        return CommUtil.isNotEmptyAndNull(name) ? name : stationId;
    }
}
