package com.kym.service.enplus.impl;

import cn.hutool.extra.mail.MailUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.kym.entity.admin.MonitorLog;
import com.kym.entity.common.RedisKeys;
import com.kym.entity.enplus.EnConnectorStatusInfo;
import com.kym.entity.miniapp.ChargeOrder;
import com.kym.entity.miniapp.WalletDetail;
import com.kym.service.admin.MonitorLogService;
import com.kym.service.enplus.EnNotifyService;
import com.kym.service.enplus.EnPlusService;
import com.kym.service.miniapp.AccountService;
import com.kym.service.miniapp.ChargeOrderService;
import com.kym.service.miniapp.WalletDetailService;
import com.kym.service.utils.KymCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;

/**
 * @author skyline
 * @description
 * @date 2023-08-04 14:26
 */

@Service
public class EnNotifyServiceImpl implements EnNotifyService {
    private static final Logger LOGGER = LoggerFactory.getLogger(EnNotifyServiceImpl.class);

    private final EnPlusService enPlusService;

    private final ChargeOrderService chargeOrderService;

    private final AccountService accountService;

    private final WalletDetailService walletDetailService;

    private final MonitorLogService monitorLogService;

    private final KymCache kymCache;

    private final RedisTemplate<String, String> redisTemplate;

    @Value("${kym.notify-email}")
    private String notifyEmail;

    public EnNotifyServiceImpl(EnPlusService enPlusService, ChargeOrderService chargeOrderService, AccountService accountService, WalletDetailService walletDetailService, MonitorLogService monitorLogService, KymCache kymCache, RedisTemplate<String, String> redisTemplate) {
        this.enPlusService = enPlusService;
        this.chargeOrderService = chargeOrderService;
        this.accountService = accountService;
        this.walletDetailService = walletDetailService;
        this.monitorLogService = monitorLogService;
        this.kymCache = kymCache;
        this.redisTemplate = redisTemplate;
    }


    /**
     * EN+ 充电站设备状态变化推送
     *
     * @param json
     * @return
     */
    @Override
    @DS("db-admin")
    public String handleNotificationStationStatus(JSONObject json) {
        var data = enPlusService.signValidation(json);
        LOGGER.info("【EN+推送】收到充电桩设备状态变化推送：\n{} \n 解密数据：{}", json, data);
        // 更新数据库，存入redis,发送邮件通知
        var connectorStatusInfo = JSONObject.parseObject(data).getJSONObject("ConnectorStatusInfo").toJavaObject(EnConnectorStatusInfo.class);
        if (connectorStatusInfo.getStatus() == 0) {
            LOGGER.info("充电桩设备离线：{}", connectorStatusInfo.getConnectorId());
            // 如果设备离线，则存入redis，有效期24h
            var monitorLog = new MonitorLog()
                    .setStationId(kymCache.getStationId(connectorStatusInfo.getConnectorId()))
                    .setSn(connectorStatusInfo.getConnectorId())
                    .setOfflineTime(LocalDateTime.now())
                    .setType(2)
                    .setOfflineStatus(connectorStatusInfo.getStatus());
            monitorLogService.save(monitorLog);
            redisTemplate.opsForValue().set(RedisKeys.OFFLINE.concat(connectorStatusInfo.getConnectorId()), "", 1, TimeUnit.DAYS);
            // TODO: 2023-09-14 通知后面改队列处理，统计站点离线总数量 
            MailUtil.send(notifyEmail, "【设备离线通知】", "站点:%s,设备%s离线"
                    .formatted(kymCache.getStationName(monitorLog.getStationId()), kymCache.getShortId(monitorLog.getSn())), false);
        } else {
            // 查询redis是否有记录，则是之前离线的机器上线了,有就删除并更新数据库恢复时间
            var exist = redisTemplate.hasKey(RedisKeys.OFFLINE.concat(connectorStatusInfo.getConnectorId()));
            if (Boolean.TRUE.equals(exist)) {
                monitorLogService.lambdaUpdate()
                        .eq(MonitorLog::getSn, connectorStatusInfo.getConnectorId())
                        .eq(MonitorLog::getIsRecover, MonitorLog.IS_RECOVER_未恢复) // 未恢复的记录
                        .set(MonitorLog::getRecoverTime, LocalDateTime.now())
                        .set(MonitorLog::getIsRecover, MonitorLog.IS_RECOVER_已恢复) // 设置为已恢复
                        .update();
                redisTemplate.delete(RedisKeys.OFFLINE.concat(connectorStatusInfo.getConnectorId()));
                MailUtil.send(notifyEmail, "【设备上线通知】", "站点:%s,设备%s恢复上线"
                        .formatted(kymCache.getStationNameByConnectorId(connectorStatusInfo.getConnectorId()), kymCache.getShortId(connectorStatusInfo.getConnectorId())), false);
            }
        }

        return """
                {
                    "Status":%d
                }
                """.formatted(0);
    }


    /**
     * EN+ 推送启动充电结果
     *
     * @param json
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String handleNotificationStartChargeResult(JSONObject json) {
        var data = enPlusService.signValidation(json);
        LOGGER.info("【EN+推送】收到启动充电结果推送：\n{}，解密数据：\n{}", json, data);
        var obj = JSONObject.parseObject(data);
        var startChargeSeq = obj.getString("StartChargeSeq");
        var startChargeSeqStat = obj.getIntValue("StartChargeSeqStat");
        var connectorId = obj.getString("ConnectorID");
        var startTime = LocalDateTime.parse(obj.getString("StartTime"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        // 更新订单状态
        UpdateWrapper<ChargeOrder> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("connector_id", connectorId);
        updateWrapper.eq("start_charge_seq", startChargeSeq);
        updateWrapper.set("charge_status", startChargeSeqStat);
        updateWrapper.set("start_time", startTime);
        chargeOrderService.update(updateWrapper);
        return """
                {
                    "StartChargeSeq":"%s",
                    "SuccStat":%d,
                    "FailReason":%d
                }
                """.formatted(startChargeSeq, 0, 0);
    }

    /**
     * 推送充电状态，1分钟推送一次
     *
     * @param json
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String handleNotificationEquipChargeStatus(JSONObject json) {
        var dataStr = enPlusService.signValidation(json);
        var data = JSONObject.parseObject(dataStr);
        LOGGER.info("【EN+推送】收到充电状态推送：\n{}，解密数据：\n{}", json, data);
        var startChargeSeq = data.getString("StartChargeSeq");
        var chargeOrder = chargeOrderService.getChargingOrderByStartChargeSeq(startChargeSeq);
        // 更新订单信息
        chargeOrder.setSoc(data.getDoubleValue("Soc"));
        chargeOrder.setTotalPower(data.getDoubleValue("TotalPower"));
        chargeOrder.setTotalMoney((data.getBigDecimal("TotalMoney").multiply(BigDecimal.valueOf(100))).intValue());
        chargeOrder.setElecMoney((data.getBigDecimal("ElecMoney").multiply(BigDecimal.valueOf(100))).intValue());
        chargeOrder.setServiceMoney((data.getBigDecimal("SeviceMoney").multiply(BigDecimal.valueOf(100))).intValue()); // 这里文档service单词错误，按文档填写
        chargeOrder.setSumPeriod(data.getIntValue("SumPeriod"));
        chargeOrder.setChargeDetail(data.getString("ChargeDetails"));
        chargeOrder.setChargeStatus(data.getIntValue("StartChargeSeqStat"));
        // TODO: 2023-09-07 待优化点 EN+一分钟推送一次，同时充电人数多的时候写入数据库过于频繁
        chargeOrderService.updateById(chargeOrder);
        return """
                {
                    "StartChargeSeq":"%s",
                    "SuccStat":%d
                }
                """.formatted(startChargeSeq, 0);
    }


    /**
     * 推送停止充电结果
     *
     * @param json
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String handleNotificationStopChargeResult(JSONObject json) {
        var dataStr = enPlusService.signValidation(json);
        var data = JSONObject.parseObject(dataStr);
        LOGGER.info("【EN+推送】收到停止充电结果推送：\n{}，解密数据：\n{}", json, data);
        var startChargeSeq = data.getString("StartChargeSeq");
        var chargeOrder = chargeOrderService.getChargingOrderByStartChargeSeq(startChargeSeq);
        if (data.containsKey("SuccStat") && data.getIntValue("SuccStat") == 0) {
            chargeOrder.setChargeStatus(data.getIntValue("StartChargeSeqStat"));
            chargeOrderService.updateById(chargeOrder);
        }
        return """
                {
                    "StartChargeSeq":"%s",
                    "SuccStat":%d,
                    "FailReason":%d
                }
                """.formatted(startChargeSeq, 0, 0);
    }


    /**
     * 推送充电订单信息
     *
     * @param json
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String handleNotificationChargeOrderInfo(JSONObject json) {
        var dataStr = enPlusService.signValidation(json);
        var data = JSONObject.parseObject(dataStr);
        LOGGER.info("【EN+推送】收到充电订单信息推送：\n{}，解密数据：\n{}", json, data);
        var startChargeSeq = data.getString("StartChargeSeq");
        var chargeOrder = chargeOrderService.getChargingOrderByStartChargeSeq(startChargeSeq);
        // EN+平台推送重试策略是当天失败第二天再推送一次，仅此一次。EN+订单页面可以多次手动推送，所以这里要先判断订单状态，避免重复处理。
        if (chargeOrder.getChargeStatus() != ChargeOrder.CHARGE_STATUS_已结束 && chargeOrder.getOrderStatus() != ChargeOrder.ORDER_STATUS_成功) {
            // 更新订单信息
            chargeOrder.setEndTime(LocalDateTime.parse(data.getString("EndTime"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            chargeOrder.setTotalPower(data.getDoubleValue("TotalPower"));
            chargeOrder.setElecMoney((int) Math.round(data.getDouble("TotalElecMoney") * 100));
            chargeOrder.setServiceMoney((int) Math.round(data.getDoubleValue("TotalSeviceMoney") * 100)); // 这里文档service单词错误，按文档填写
            chargeOrder.setTotalMoney((int) Math.round(data.getDoubleValue("TotalMoney") * 100));
            chargeOrder.setStopReason(data.getIntValue("StopReason"));
            chargeOrder.setSumPeriod(data.getIntValue("SumPeriod"));
            chargeOrder.setChargeDetail(data.getString("ChargeDetails"));
            // 订单成功
            chargeOrder.setOrderStatus(ChargeOrder.ORDER_STATUS_成功);
            // 充电结束
            chargeOrder.setChargeStatus(ChargeOrder.CHARGE_STATUS_已结束);
            chargeOrderService.updateById(chargeOrder);

            // 扣费
            var account = accountService.getAccountByUserId(chargeOrder.getUserId());
            account.setBalance(account.getBalance() - chargeOrder.getTotalMoney());
            accountService.updateById(account);

            // 记录资金流水
            var walletDetail = new WalletDetail();
            walletDetail.setUserId(chargeOrder.getUserId());
            walletDetail.setOrderNo(startChargeSeq);
            // 消费
            walletDetail.setType(WalletDetail.TYPE_消费);
            walletDetail.setAmount(chargeOrder.getTotalMoney());
            walletDetail.setTransactionTime(LocalDateTime.parse(data.getString("EndTime"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            // 已确认
            walletDetail.setStatus(WalletDetail.STATUS_已确认);
            walletDetailService.save(walletDetail);
        }
        return """
                {
                    "StartChargeSeq":"%s",
                    "ConnectorID":"%s",
                    "ConfirmResult":%d
                }
                """.formatted(startChargeSeq, chargeOrder.getConnectorId(), 0);
    }
}
