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.common.cache.ConnectorStatusCache;
import com.kym.common.config.RedisDBChangeUtil;
import com.kym.entity.admin.ConnectorInfo;
import com.kym.entity.admin.EquipmentInfo;
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.OrderRechargeRights;
import com.kym.entity.miniapp.UserRechargeRights;
import com.kym.entity.miniapp.WalletDetail;
import com.kym.service.admin.ConnectorInfoService;
import com.kym.service.admin.EquipmentInfoService;
import com.kym.service.admin.MonitorLogService;
import com.kym.service.cache.KymCache;
import com.kym.service.enplus.EnNotifyService;
import com.kym.service.enplus.EnPlusService;
import com.kym.service.miniapp.*;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.stream.Collectors;

/**
 * @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 ChargeService chargeService;

    private final AccountService accountService;

    private final WalletDetailService walletDetailService;

    private final MonitorLogService monitorLogService;

    private final EquipmentInfoService equipmentInfoService;

    private final ConnectorInfoService connectorInfoService;

    private final UserRechargeRightsService userRechargeRightsService;

    private final OrderRechargeRightsService orderRechargeRightsService;

    private final RedisDBChangeUtil redisDBChangeUtil;

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

    public EnNotifyServiceImpl(EnPlusService enPlusService, ChargeOrderService chargeOrderService,
                               ChargeService chargeService, AccountService accountService, WalletDetailService walletDetailService,
                               MonitorLogService monitorLogService, EquipmentInfoService equipmentInfoService,
                               ConnectorInfoService connectorInfoService, UserRechargeRightsService userRechargeRightsService, OrderRechargeRightsService orderRechargeRightsService, RedisDBChangeUtil redisDBChangeUtil) {
        this.enPlusService = enPlusService;
        this.chargeOrderService = chargeOrderService;
        this.chargeService = chargeService;
        this.accountService = accountService;
        this.walletDetailService = walletDetailService;
        this.monitorLogService = monitorLogService;
        this.equipmentInfoService = equipmentInfoService;
        this.connectorInfoService = connectorInfoService;
        this.userRechargeRightsService = userRechargeRightsService;
        this.orderRechargeRightsService = orderRechargeRightsService;
        this.redisDBChangeUtil = redisDBChangeUtil;
    }

    @PostConstruct
    void init() {
        ConnectorStatusCache.INSTANCE.putAll(equipmentInfoService.list().stream().collect(Collectors.toMap(item -> item.getEquipmentId().concat("1"), EquipmentInfo::getServiceStatus)));
    }

    /**
     * EN+ 充电站设备状态变化推送
     *
     * @param json
     * @return
     */
    @Override
    @DS("db-admin")
    public String handleNotificationStationStatus(JSONObject json) {
        var data = enPlusService.signValidation(json);
        LOGGER.info("【EN+推送】收到充电桩设备状态变化推送:{},解密数据：{}", json, data);
        // 更新数据库，存入redis,发送邮件通知
        var connectorStatusInfo = JSONObject.parseObject(data).getJSONObject("ConnectorStatusInfo").toJavaObject(EnConnectorStatusInfo.class);
        var connectorId = connectorStatusInfo.getConnectorId();
        var equipmentId = connectorId.substring(0, 16);

        equipmentInfoService.lambdaUpdate()
                .eq(EquipmentInfo::getEquipmentId, equipmentId)
                .set(EquipmentInfo::getServiceStatus, connectorStatusInfo.getStatus())
                .update();

        connectorInfoService.lambdaUpdate()
                .eq(ConnectorInfo::getConnectorId, connectorId)
                .set(ConnectorInfo::getStatus, connectorStatusInfo.getStatus())
                .update();

        // 切换到admin对应的db
        redisDBChangeUtil.setDataBase(0);

        var connectorStatus = connectorStatusInfo.getStatus();

        if (connectorStatus == 0) {
            LOGGER.info("充电桩设备离线：{}", connectorStatusInfo.getConnectorId());
            // 如果设备离线，则存入redis
            var monitorLog = new MonitorLog()
                    .setStationId(KymCache.INSTANCE.getStationIdByEquipmentIdOrConnectorId(connectorStatusInfo.getConnectorId()))
                    .setSn(connectorStatusInfo.getConnectorId())
                    .setOfflineTime(LocalDateTime.now())
                    .setType(2)
                    .setOfflineStatus(connectorStatusInfo.getStatus());
            monitorLogService.save(monitorLog);

            // 离线设备放入队列，5分钟之后如果还未恢复则放入长时间离线设备集合中并发送提醒，上线后发送提醒
            redisDBChangeUtil.redisTemplate.opsForZSet().add(RedisKeys.OFFLINE, connectorStatusInfo.getConnectorId(), System.currentTimeMillis() + 5 * 60 * 1000);
        } else {
            // 先删除离线设备队列的记录,再删除离线超时队列中的记录
            var isDelete = redisDBChangeUtil.redisTemplate.opsForZSet().remove(RedisKeys.OFFLINE, connectorStatusInfo.getConnectorId());
            var exist = redisDBChangeUtil.redisTemplate.opsForSet().remove(RedisKeys.OFFLINE_EXPIRED, connectorStatusInfo.getConnectorId());
            if ((isDelete != null && isDelete > 0) || (exist != null && exist > 0)) {
                // 更新设备监控表
                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();
            }
            if (exist != null && exist > 0) {
                MailUtil.send(notifyEmail, "【设备上线通知】", "站点:%s,设备%s恢复上线"
                        .formatted(KymCache.INSTANCE.getStationNameById(connectorStatusInfo.getConnectorId()), KymCache.INSTANCE.getShortIdByEquipmentIdOrConnectorId(connectorStatusInfo.getConnectorId())), false);
            }
        }

        // 设备状态变为空闲，校验是否有预约订单，有则清除预约订单数据。
        if (connectorStatus == 1 && ConnectorStatusCache.INSTANCE.get(connectorId) != 1) {
            LOGGER.info("设备:{}状态转为空闲，清除设备预约数据...", connectorId);
            chargeService.cancelBookingByConnector(connectorId);
        }
        ConnectorStatusCache.INSTANCE.put(connectorId, connectorStatus);

        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+推送】收到启动充电结果推送：{}，解密数据：{}", 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("order_status", ChargeOrder.ORDER_STATUS_未知);
        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+推送】收到充电状态推送：{}，解密数据：{}", 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+推送】收到停止充电结果推送：{}，解密数据：{}", 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+推送】收到充电订单信息推送：{}，解密数据：{}", json, data);
        var startChargeSeq = data.getString("StartChargeSeq");
        var chargeOrder = chargeOrderService.getChargingOrderByStartChargeSeq(startChargeSeq);

        // 账户
        var account = accountService.getAccountByUserId(chargeOrder.getUserId());

        // EN+平台推送重试策略是当天失败第二天再推送一次，仅此一次。EN+订单页面可以多次手动推送，所以这里要先判断订单状态，避免重复处理。
        if (chargeOrder.getChargeStatus() != ChargeOrder.CHARGE_STATUS_已结束 || chargeOrder.getOrderStatus() != ChargeOrder.ORDER_STATUS_成功) {
            // 更新订单信息
            chargeOrder
                    .setStartTime(LocalDateTime.parse(data.getString("StartTime"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                    .setEndTime(LocalDateTime.parse(data.getString("EndTime"), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")))
                    .setTotalPower(data.getDoubleValue("TotalPower"))
                    .setElecMoney((int) Math.round(data.getDouble("TotalElecMoney") * 100))
                    .setServiceMoney((int) Math.round(data.getDoubleValue("TotalSeviceMoney") * 100)) // 这里文档service单词错误，按文档填写
                    .setTotalMoney((int) Math.round(data.getDoubleValue("TotalMoney") * 100))
                    .setStopReason(data.getIntValue("StopReason"))
                    .setSumPeriod(data.getIntValue("SumPeriod"))
                    .setChargeDetail(data.getString("ChargeDetails"))
                    // 实付金额初始化为订单总金额
                    .setPayAmount(chargeOrder.getTotalMoney());

            // 获取订单权益，计算订单优惠金额，实付金额，服务费优惠金额
            var orderRechargeRights = orderRechargeRightsService.lambdaQuery()
                    .eq(OrderRechargeRights::getStartChargeSeq, chargeOrder.getStartChargeSeq())
                    .eq(OrderRechargeRights::getUserId, chargeOrder.getUserId())
                    .one();
            if (orderRechargeRights != null) {
                // 获取折扣，计算优惠
                var discount = orderRechargeRights.getDiscount();
                var serviceMoneyDiscount = (int) (chargeOrder.getServiceMoney() * (BigDecimal.valueOf(1).subtract(BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100)))).doubleValue()); // 只保留到分，分以下不进行四舍五入
                chargeOrder
                        .setPayAmount(chargeOrder.getTotalMoney() - serviceMoneyDiscount)
                        .setDiscountAmount(serviceMoneyDiscount)
                        .setServiceMoneyDiscount(serviceMoneyDiscount);

                orderRechargeRights.setDiscountAmount(serviceMoneyDiscount);

                // 用户充值权益
                var userRechargeRights = userRechargeRightsService.lambdaQuery()
                        .eq(UserRechargeRights::getId, orderRechargeRights.getUserRightsId())
                        .eq(UserRechargeRights::getUserId, chargeOrder.getUserId())
                        .eq(UserRechargeRights::getRightsId, orderRechargeRights.getRightsId())
                        .one();

                // 用户当前权益金余额是否足够支付本次充电费用
                if (userRechargeRights.getStatus() == UserRechargeRights.STATUS_有效) {
                    if (chargeOrder.getPayAmount() <= userRechargeRights.getRightsBalance()) {
                        userRechargeRights
                                // 用户权益包余额扣减当前订单实付金额
                                .setRightsBalance(userRechargeRights.getRightsBalance() - chargeOrder.getPayAmount())
                                .setDiscountAmount(userRechargeRights.getDiscountAmount() + serviceMoneyDiscount);

                        // 账户设置优惠不可退金额
                        account.setDiscountAmount(account.getDiscountAmount() + serviceMoneyDiscount);
                        if (chargeOrder.getPayAmount().intValue() == userRechargeRights.getRightsBalance().intValue()) {
                            userRechargeRights.setStatus(UserRechargeRights.STATUS_无效);
                        }
                    } else {
                        // 不足(用户当前权益金余额是否足够支付本次充电费用)
                        // 如果优惠后的实付金额大于权益金余额，那么实际优惠金额需要通过权益金余额反推
                        // 通过权益金余额，反推最多扣减多少服务费
                        // rightBalance = elecMoney + serviceMoney * (1-discount)
                        // 订单中elecMoney与serviceMoney的比值是一定的
                        var ratio = BigDecimal.valueOf(chargeOrder.getElecMoney()).divide(BigDecimal.valueOf(chargeOrder.getServiceMoney()), 2, RoundingMode.HALF_UP);
                        // (ratio * realServiceMoney) + (realServiceMoney * (100 - discount)/100) = rightBalance
                        var discountRate = BigDecimal.valueOf(discount).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
                        var userRightBalance = BigDecimal.valueOf(userRechargeRights.getRightsBalance());
                        // 真实可以覆盖的服务费金额
                        var realServiceMoney = userRightBalance.divide(ratio.add(BigDecimal.valueOf(1)).subtract(discountRate), 2, RoundingMode.HALF_UP);
                        // 实际优惠金额 = 实际可以覆盖的服务费金额 * 折扣率
                        var realDiscountAmount = realServiceMoney.multiply(BigDecimal.valueOf(1).subtract(discountRate)).intValue();
                        chargeOrder
                                // 实付金额
                                .setPayAmount(chargeOrder.getTotalMoney() - realDiscountAmount)
                                .setDiscountAmount(realDiscountAmount)
                                .setServiceMoneyDiscount(realDiscountAmount);
                        // 余额扣完,总优惠金额加上最后一次真实优惠金额，此条用户权益结束，状态设置为无效
                        userRechargeRights
                                .setRightsBalance(0)
                                .setDiscountAmount(userRechargeRights.getDiscountAmount() + realDiscountAmount)
                                .setStatus(UserRechargeRights.STATUS_无效);

                        orderRechargeRights.setDiscountAmount(realDiscountAmount);
                        // 此权益包消耗完毕，账户设置优惠不可退金额
                        account.setDiscountAmount(account.getDiscountAmount() + realDiscountAmount);
                    }
                }

                // 更新订单权益的最终优惠金额
                orderRechargeRightsService.updateById(orderRechargeRights);
                // 更新用户权益金等
                userRechargeRightsService.updateById(userRechargeRights);
            }

            // 优惠券（将来如果有多种活动形式，进行链式处理）

            // 订单成功
            chargeOrder.setOrderStatus(ChargeOrder.ORDER_STATUS_成功);
            // 充电结束
            chargeOrder.setChargeStatus(ChargeOrder.CHARGE_STATUS_已结束);
            chargeOrderService.updateById(chargeOrder);

            // 账户扣费
            account.setBalance(account.getBalance() - chargeOrder.getPayAmount());
            accountService.updateById(account);

            // 记录资金流水
            var walletDetail = new WalletDetail();
            walletDetail.setUserId(chargeOrder.getUserId());
            walletDetail.setOrderNo(startChargeSeq);
            // 消费
            walletDetail.setType(WalletDetail.TYPE_消费);
            walletDetail.setAmount(chargeOrder.getPayAmount());
            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);
    }
}
