package com.ruoyi.ss.timeBill.service.impl;

import com.ruoyi.common.core.redis.RedisLock;
import com.ruoyi.common.core.redis.enums.RedisLockKey;
import com.ruoyi.common.pay.wx.service.WxPayService;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ServiceUtil;
import com.ruoyi.common.utils.SnowFlakeUtil;
import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.iot.service.IotService;
import com.ruoyi.ss.device.domain.vo.DeviceVO;
import com.ruoyi.ss.device.service.DeviceService;
import com.ruoyi.ss.payBill.domain.PayBillVO;
import com.ruoyi.ss.payBill.domain.vo.DoPayVO;
import com.ruoyi.ss.payBill.interfaces.AfterPay;
import com.ruoyi.ss.payBill.service.PayBillConverter;
import com.ruoyi.ss.payBill.service.PayBillService;
import com.ruoyi.ss.suit.domain.enums.SuitTimeUnit;
import com.ruoyi.ss.timeBill.domain.TimeBill;
import com.ruoyi.ss.timeBill.domain.TimeBillQuery;
import com.ruoyi.ss.timeBill.domain.TimeBillVO;
import com.ruoyi.ss.timeBill.domain.dto.TimeBillAddOrderDTO;
import com.ruoyi.ss.timeBill.domain.dto.TimeBillPayDTO;
import com.ruoyi.ss.timeBill.domain.enums.TimeBillStatus;
import com.ruoyi.ss.timeBill.mapper.TimeBillMapper;
import com.ruoyi.ss.timeBill.service.TimeBillConverter;
import com.ruoyi.ss.timeBill.service.TimeBillService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.List;

/**
 * 时长订单Service业务层处理
 *
 * @author ruoyi
 * @date 2024-07-24
 */
@Service
public class TimeBillServiceImpl implements TimeBillService, AfterPay
{
    @Autowired
    private TimeBillMapper timeBillMapper;

    @Autowired
    private RedisLock redisLock;

    @Autowired
    private DeviceService deviceService;

    @Autowired
    private TimeBillConverter timeBillConverter;

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private IotService iotService;

    @Autowired
    private PayBillService payBillService;

    @Autowired
    private WxPayService wxPayService;

    @Autowired
    private PayBillConverter payBillConverter;

    /**
     * 查询时长订单
     *
     * @param billId 时长订单主键
     * @return 时长订单
     */
    @Override
    public TimeBillVO selectTimeBillByBillId(Long billId)
    {
        return timeBillMapper.selectTimeBillByBillId(billId);
    }

    /**
     * 查询时长订单列表
     *
     * @param timeBill 时长订单
     * @return 时长订单
     */
    @Override
    public List<TimeBillVO> selectTimeBillList(TimeBillQuery timeBill)
    {
        return timeBillMapper.selectTimeBillList(timeBill);
    }

    /**
     * 新增时长订单
     *
     * @param timeBill 时长订单
     * @return 结果
     */
    @Override
    public int insertTimeBill(TimeBill timeBill)
    {
        timeBill.setCreateTime(DateUtils.getNowDate());
        timeBill.setBillNo(String.valueOf(SnowFlakeUtil.newId()));
        return timeBillMapper.insertTimeBill(timeBill);
    }

    /**
     * 修改时长订单
     *
     * @param timeBill 时长订单
     * @return 结果
     */
    @Override
    public int updateTimeBill(TimeBill timeBill)
    {
        return timeBillMapper.updateTimeBill(timeBill);
    }

    /**
     * 批量删除时长订单
     *
     * @param billIds 需要删除的时长订单主键
     * @return 结果
     */
    @Override
    public int deleteTimeBillByBillIds(Long[] billIds)
    {
        return timeBillMapper.deleteTimeBillByBillIds(billIds);
    }

    /**
     * 删除时长订单信息
     *
     * @param billId 时长订单主键
     * @return 结果
     */
    @Override
    public int deleteTimeBillByBillId(Long billId)
    {
        return timeBillMapper.deleteTimeBillByBillId(billId);
    }

    /**
     * 下单
     * @param dto 参数
     * @return 订单编号
     */
    @Override
    public String addOrder(TimeBillAddOrderDTO dto) {
        ServiceUtil.assertion(dto == null, "下单失败，参数不允许为空");
        ServiceUtil.assertion(dto.getDeviceId() == null, "下单失败，设备不存在");
        Long lockKey = dto.getDeviceId();
        ServiceUtil.assertion(!redisLock.lock(RedisLockKey.ADD_TIME_BILL, lockKey), "当前使用该设备的人数过多，请稍后再试");
        try {
            // 查询设备
            DeviceVO device = deviceService.selectById(dto.getDeviceId());
            ServiceUtil.assertion(device == null, "设备不存在");
            dto.setDevice(device);

            // 判断设备是否已经被使用
            List<Long> userIds = deviceService.selectDeviceUsingUser(dto.getDeviceId());
            ServiceUtil.assertion(CollectionUtils.isNotEmptyElement(userIds), "设备正在使用中，无法下单");

            // DTO 转为订单 PO
            TimeBill po = timeBillConverter.toPo(dto);

            transactionTemplate.execute(status -> {
                // 下单
                int insert = this.insertTimeBill(po);
                ServiceUtil.assertion(insert != 1, "下单失败，请稍后再试");

                // 开启设备
                boolean open = iotService.open(device);
                ServiceUtil.assertion(!open, "开启设备失败，请稍后再试");

                return insert;
            });

            return po.getBillNo();
        } finally {
            redisLock.unlock(RedisLockKey.ADD_TIME_BILL, lockKey);
        }
    }

    @Override
    public List<TimeBillVO> selectUsingList(TimeBillQuery query) {
        query.setStatus(TimeBillStatus.USING.getStatus());
        return this.selectTimeBillList(query);
    }

    @Override
    public int logicDel(List<Long> billIds) {
        return timeBillMapper.logicDel(billIds);
    }

    @Override
    public int close(TimeBillVO bill) {
        if (bill == null) {
            return 0;
        }

        Integer result = transactionTemplate.execute(status -> {
            // 修改订单状态和结束信息
            LocalDateTime endTime = LocalDateTime.now();
            TimeBill data = new TimeBill();
            data.setStatus(TimeBillStatus.WAIT_PAY.getStatus());
            data.setEndTime(endTime);
            data.setAmount(this.calcAmount(bill, bill.getStartTime(), endTime));
            TimeBillQuery query = new TimeBillQuery();
            query.setStatus(TimeBillStatus.USING.getStatus());
            query.setBillId(bill.getBillId());
            int update = this.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "订单状态已发生变化，请刷新后重试");

            // 关闭设备
            DeviceVO device = deviceService.selectById(bill.getDeviceId());
            ServiceUtil.assertion(device == null, "设备不存在");
            boolean close = iotService.close(device);
            ServiceUtil.assertion(!close, "关闭设备失败，请检查设备是否在线");

            return update;
        });

        return result == null ? 0 : result;
    }

    /**
     * 计算价格
     * @param bill 订单信息
     * @param startTime 启用时间
     * @param endTime 结束时间
     */
    private BigDecimal calcAmount(TimeBillVO bill, LocalDateTime startTime, LocalDateTime endTime) {
        ServiceUtil.assertion(bill == null || startTime == null || endTime == null, "计算金额出错：参数错误");
        ServiceUtil.assertion(endTime.isBefore(startTime), "时长出错");

        Duration between = Duration.between(startTime, endTime);
        // 总使用时长（秒）
        long seconds = between.getSeconds();

        // 总金额 = 起步费
        BigDecimal totalPrice = bill.getDeviceStartPrice();
        // 起步时长
        SuitTimeUnit startUnit = SuitTimeUnit.getByValue(bill.getDeviceStartUnit());
        ServiceUtil.assertion(startUnit == null, "未知的起步时长单位");
        long startSeconds = startUnit.getConversion() * bill.getDeviceStartTime();

        // 额外费用
        seconds = seconds - startSeconds;   // 剩余未结算的时长
        if (seconds > 0) {
            // 计算一个周期的时长（秒）
            SuitTimeUnit overUnit = SuitTimeUnit.getByValue(bill.getDeviceOverUnit());
            ServiceUtil.assertion(overUnit == null, "未知的续期时长单位");
            long rangeTime = overUnit.getConversion() * bill.getDeviceOverTime();

            // 计算周期数量（不足一个周期算一个周期）
            long rangeCount = seconds / rangeTime + (seconds % rangeTime > 0 ? 1 : 0);

            // 总金额 + 周期金额
            totalPrice = totalPrice.add(new BigDecimal(rangeCount).multiply(bill.getDeviceOverPrice()));
        }

        return totalPrice;
    }

    @Override
    public int updateByQuery(TimeBill data, TimeBillQuery query) {
        if (data == null || query == null) {
            return 0;
        }
        return timeBillMapper.updateByQuery(data, query );
    }

    @Override
    public DoPayVO pay(TimeBillPayDTO dto) {
        TimeBillVO bill = selectTimeBillByBillId(dto.getBillId());
        ServiceUtil.assertion(bill == null || bill.getBillId() == null, "参数错误");

        return transactionTemplate.execute(status -> {
            // 修改订单状态
            TimeBill data = new TimeBill();
            data.setStatus(TimeBillStatus.PAYING.getStatus());
            TimeBillQuery query = new TimeBillQuery();
            query.setBillId(bill.getBillId());
            query.setStatusList(TimeBillStatus.getCanPayList());
            int update = this.updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "订单状态已发生变化，请稍后重试");

            // 创建支付订单并支付
            return payBillService.createPayBill(payBillConverter.toPo(bill, dto.getChannelId()));
        });
    }

    @Override
    public int onPaySuccess(PayBillVO payBill) {
        // 判断订单是否存在
        TimeBillVO bill = selectTimeBillByBillId(payBill.getBstId());
        ServiceUtil.assertion(bill == null, "订单不存在");

        Integer result = transactionTemplate.execute(status -> {
            // 修改订单信息
            TimeBill data = new TimeBill();
            data.setStatus(TimeBillStatus.PAY_SUCCESS.getStatus());
            data.setPayId(payBill.getPayId());
            TimeBillQuery query = new TimeBillQuery();
            query.setStatus(TimeBillStatus.PAYING.getStatus());
            query.setBillId(bill.getBillId());
            int update = updateByQuery(data, query);
            ServiceUtil.assertion(update != 1, "订单状态已发生变化，请刷新后重试");

            return update;
        });

        return result == null ? 0 : result;
    }

}
