package com.yc.ship.module.product.service.voyage;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import com.yc.ship.framework.common.pojo.PageResult;
import com.yc.ship.framework.common.util.collection.CollectionUtils;
import com.yc.ship.framework.common.util.object.BeanUtils;
import com.yc.ship.module.product.controller.admin.pricetemplate.vo.*;
import com.yc.ship.module.product.controller.admin.pricevoyage.vo.PriceVoyageSaveReqVO;
import com.yc.ship.module.product.controller.admin.voyage.vo.*;
import com.yc.ship.module.product.controller.app.voyage.vo.AppVoyageDayRespVO;
import com.yc.ship.module.product.dal.dataobject.basicpricearea.BasicPriceAreaDO;
import com.yc.ship.module.product.dal.dataobject.pricearea.PriceAreaDO;
import com.yc.ship.module.product.dal.dataobject.pricefloor.PriceFloorDO;
import com.yc.ship.module.product.dal.dataobject.priceroommodel.PriceRoomModelDO;
import com.yc.ship.module.product.dal.dataobject.priceroommodeltype.PriceRoomModelTypeDO;
import com.yc.ship.module.product.dal.dataobject.pricesinglesetting.PriceSingleSettingDO;
import com.yc.ship.module.product.dal.dataobject.pricespu.PriceSpuDO;
import com.yc.ship.module.product.dal.dataobject.voyage.VoyageDO;
import com.yc.ship.module.product.dal.dataobject.voyagestock.VoyageStockDO;
import com.yc.ship.module.product.dal.dataobject.voyagestockdetail.VoyageStockDetailDO;
import com.yc.ship.module.product.dal.mysql.pricevoyage.PriceVoyageMapper;
import com.yc.ship.module.product.dal.mysql.voyage.VoyageMapper;
import com.yc.ship.module.product.dal.mysql.voyagestock.VoyageStockMapper;
import com.yc.ship.module.product.dal.mysql.voyagestockdetail.VoyageStockDetailMapper;
import com.yc.ship.module.product.dal.mysql.voyagestockdistribute.VoyageStockDistributeNewMapper;
import com.yc.ship.module.product.enums.UseStatusEnum;
import com.yc.ship.module.product.enums.YesOrNoEnum;
import com.yc.ship.module.product.framework.lock.ProductRedisKeyConstants;
import com.yc.ship.module.product.service.pricetemplate.PriceTemplateService;
import com.yc.ship.module.product.service.pricevoyage.PriceVoyageService;
import com.yc.ship.module.product.utils.VoyageUUCodeUtils;
import com.yc.ship.module.resource.api.ship.ShipApi;
import com.yc.ship.module.resource.api.ship.dto.RoomModelFloorNumDTO;
import com.yc.ship.module.resource.api.ship.dto.ShipRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.yc.ship.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.yc.ship.module.product.enums.ErrorCodeConstants.VOYAGE_NOT_EXISTS;

/**
 * 航次管理 Service 实现类
 *
 * @author lfy
 */
@Service
@Validated
@Slf4j
public class VoyageServiceImpl implements VoyageService {

    @Resource
    private VoyageMapper voyageMapper;

    @Resource
    private VoyageStockMapper voyageStockMapper;

    @Resource
    private ShipApi shipApi;

    @Resource
    private VoyageStockDetailMapper voyageStockDetailMapper;

    @Resource
    private PriceVoyageMapper priceVoyageMapper;

    @Resource
    private VoyageStockDistributeNewMapper voyageStockDistributeNewMapper;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private PriceTemplateService priceTemplateService;

    @Resource
    private PriceVoyageService priceVoyageService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createVoyage(VoyageSaveReqVO createReqVO) {
        // 插入
        VoyageDO voyage = BeanUtils.toBean(createReqVO, VoyageDO.class);
        Long shipId = voyage.getShipId();
        ShipRespDTO shipRespDTO = shipApi.queryShip(shipId);
        voyage.setCode(VoyageUUCodeUtils.generateVoyageNo(shipRespDTO.getCode(), voyage.getStartTime(), voyage.getBoardingAddress(), voyage.getLeaveAddress()));
        voyageMapper.insert(voyage);
        Long id = voyage.getId();

        saveOrUpdateVoyageStock(createReqVO, id);
        // 返回
        return id;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateVoyage(VoyageSaveReqVO updateReqVO) {
        // 校验存在
        validateVoyageExists(updateReqVO.getId());
        // 更新
        VoyageDO updateObj = BeanUtils.toBean(updateReqVO, VoyageDO.class);
        voyageMapper.updateById(updateObj);
        saveOrUpdateVoyageStock(updateReqVO, updateReqVO.getId());

    }

    private void saveOrUpdateVoyageStock(VoyageSaveReqVO reqVO,Long voyageId) {
        VoyageStockDO voyageStockDO = new VoyageStockDO();
        voyageStockDO.setVoyageId(voyageId);
        voyageStockDO.setVoyageName(reqVO.getName());
        voyageStockDO.setShipId(reqVO.getShipId());
        voyageStockDO.setType(reqVO.getType());

        VoyageStockDO stockDO = voyageStockMapper.selectById(voyageId);
        //现在给整个航次的库存加锁
        String lockKey = String.format(ProductRedisKeyConstants.STOCK_REDIS_KEY_PREFIX, voyageId);
        RLock lock = redissonClient.getLock(lockKey);
        try {
            lock.lock(60, TimeUnit.SECONDS);
            if(stockDO != null) {
                voyageStockMapper.updateById(voyageStockDO);
            }else {
                //新增时添加统计数据
                List<RoomModelFloorNumDTO> list = shipApi.queryRoomModelFloorToNum(reqVO.getShipId());
                int intSum = list.stream().mapToInt(RoomModelFloorNumDTO::getNum).sum();
                BigDecimal sum = BigDecimal.valueOf(intSum);
                voyageStockDO.setTotalNum(sum);
                voyageStockDO.setShareNum(BigDecimal.ZERO);
                voyageStockDO.setRealTotalNum(sum);
                voyageStockDO.setNotShelfNum(sum);
                voyageStockDO.setShelfedNum(BigDecimal.ZERO);
                voyageStockDO.setBookNum(BigDecimal.ZERO);
                voyageStockDO.setCanSellNum(BigDecimal.ZERO);
                voyageStockDO.setVirtualNum(BigDecimal.ZERO);
                voyageStockDO.setOversoldNum(BigDecimal.ZERO);
                voyageStockDO.setSurplusNum(sum);
                voyageStockDO.setShareNum(BigDecimal.ZERO);
                voyageStockDO.setVoyageId(voyageId);
                voyageStockMapper.insert(voyageStockDO);
                //添加库存详情
                List<VoyageStockDetailDO> stockDetailList = new ArrayList<>();
                list.forEach(item -> {
                    VoyageStockDetailDO stockDetailDO = new VoyageStockDetailDO();
                    stockDetailDO.setVoyageId(voyageId);
                    stockDetailDO.setRoomModelId(item.getRoomModelId());
                    stockDetailDO.setRoomModelName(item.getRoomModelName());
                    stockDetailDO.setFloor(item.getFloor());
                    stockDetailDO.setTotalNum(BigDecimal.valueOf(item.getNum()));
                    stockDetailDO.setRealTotalNum(BigDecimal.valueOf(item.getNum()));
                    stockDetailDO.setShelfNum(BigDecimal.ZERO);
                    stockDetailDO.setNotShelfNum(BigDecimal.valueOf(item.getNum()));
                    stockDetailDO.setShelfedNum(BigDecimal.ZERO);
                    stockDetailDO.setBookNum(BigDecimal.ZERO);
                    stockDetailDO.setCanSellNum(BigDecimal.ZERO);
                    stockDetailDO.setOversoldNum(BigDecimal.ZERO);
                    stockDetailDO.setVirtualNum(BigDecimal.ZERO);
                    stockDetailDO.setSurplusNum(BigDecimal.valueOf(item.getNum()));
                    stockDetailDO.setShareNum(BigDecimal.ZERO);
                    stockDetailList.add(stockDetailDO);
                });
                voyageStockDetailMapper.insertBatch(stockDetailList);
            }
        }finally {
            if(lock.isHeldByCurrentThread() && lock.isLocked()){
                lock.unlock();
            }
        }
    }

    private void saveOrUpdateVoyageStockBatch(List<VoyageDO> voyageDOList, Long shipId) {
        // 批量插入库存统计数据
        List<VoyageStockDO> voyageStockDOList = new ArrayList<>();
        //添加库存详情
        List<VoyageStockDetailDO> stockDetailList = new ArrayList<>();
        //新增时添加统计数据
        List<RoomModelFloorNumDTO> list = shipApi.queryRoomModelFloorToNum(shipId);
        int intSum = list.stream().mapToInt(RoomModelFloorNumDTO::getNum).sum();
        BigDecimal sum = BigDecimal.valueOf(intSum);
        BigDecimal zero = BigDecimal.ZERO;
        voyageDOList.forEach(item -> {
            VoyageStockDO voyageStockDO = new VoyageStockDO();
            voyageStockDO.setVoyageId(item.getId());
            voyageStockDO.setVoyageName(item.getName());
            voyageStockDO.setShipId(item.getShipId());
            voyageStockDO.setType(item.getType());
            voyageStockDO.setTotalNum(sum);
            voyageStockDO.setRealTotalNum(sum);
            voyageStockDO.setNotShelfNum(sum);
            voyageStockDO.setShelfedNum(zero);
            voyageStockDO.setBookNum(zero);
            voyageStockDO.setCanSellNum(zero);
            voyageStockDO.setVirtualNum(zero);
            voyageStockDO.setOversoldNum(zero);
            voyageStockDO.setSurplusNum(sum);
            voyageStockDO.setShareNum(zero);
            voyageStockDO.setVoyageId(item.getId());
            voyageStockDOList.add(voyageStockDO);
            list.forEach(roomModelFloorNumDTO -> {
                BigDecimal num = BigDecimal.valueOf(roomModelFloorNumDTO.getNum());
                VoyageStockDetailDO stockDetailDO = new VoyageStockDetailDO();
                stockDetailDO.setVoyageId(item.getId());
                stockDetailDO.setRoomModelId(roomModelFloorNumDTO.getRoomModelId());
                stockDetailDO.setRoomModelName(roomModelFloorNumDTO.getRoomModelName());
                stockDetailDO.setFloor(roomModelFloorNumDTO.getFloor());
                stockDetailDO.setTotalNum(num);
                stockDetailDO.setRealTotalNum(num);
                stockDetailDO.setShelfNum(zero);
                stockDetailDO.setNotShelfNum(num);
                stockDetailDO.setShelfedNum(zero);
                stockDetailDO.setBookNum(zero);
                stockDetailDO.setCanSellNum(zero);
                stockDetailDO.setOversoldNum(zero);
                stockDetailDO.setVirtualNum(zero);
                stockDetailDO.setSurplusNum(num);
                stockDetailDO.setShareNum(zero);
                stockDetailList.add(stockDetailDO);
            });
        });
        voyageStockMapper.insertBatch(voyageStockDOList);
        voyageStockDetailMapper.insertBatch(stockDetailList);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void deleteVoyage(Long id) {
        // 校验存在
        validateVoyageExists(id);
        // 删除
        voyageMapper.deleteById(id);

        //删除库存
        voyageStockMapper.deleteByVoyageId(id);

        //删除库存详情
        voyageStockDetailMapper.deleteByVoyageId(id);
        //删除航次价格
        priceVoyageMapper.deleteByVoyageId(id);

        //删除航次分销商库存
        voyageStockDistributeNewMapper.deleteByVoyageId(id);
    }

    private void validateVoyageExists(Long id) {
        if (voyageMapper.selectById(id) == null) {
            throw exception(VOYAGE_NOT_EXISTS);
        }
    }

    @Override
    public VoyageDO getVoyage(Long id) {
        return voyageMapper.selectById(id);
    }

    @Override
    public PageResult<VoyageDO> getVoyagePage(VoyagePageReqVO pageReqVO) {
        return voyageMapper.selectPage(pageReqVO);
    }

    @Override
    public List<VoyageDO> getListSimple(VoyageReqVO reqVO) {
        return voyageMapper.selectListSimple(reqVO);
    }

    @Override
    public List<VoyageDO> getList(List<Long> ids) {
        if(ids.isEmpty()) {
            return Collections.emptyList();
        }
        return voyageMapper.selectByIds(ids);
    }

    @Override
    public Map<Long, VoyageDO> getListMap(List<Long> ids) {
        List<VoyageDO> list = getList(ids);
        return CollectionUtils.convertMap(list, VoyageDO::getId);
    }

    @Override
    public List<VoyageDO> getOnShelfListByShipId(Long shipId) {
        return voyageMapper.selectVovageList(shipId);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void createVoyageBatch(VoyageSaveBatchReqVO createReqVO) {
        LocalDate startDate = createReqVO.getStartDate();
        LocalDate endDate = createReqVO.getEndDate();
        Integer voyageDays = createReqVO.getVoyageDays();
        Integer intervalDays = createReqVO.getIntervalDays();
        String startTime = createReqVO.getStartTime();
        String leaveTime = createReqVO.getLeaveTime();
        String boardingTime = createReqVO.getBoardingTime();
        Long shipId = createReqVO.getShipId();
        Long routeId = createReqVO.getRouteId();
        Long templateId = createReqVO.getTemplateId();
        Integer startIntervalDays = createReqVO.getStartIntervalDays();
        Integer leaveIntervalDays = createReqVO.getLeaveIntervalDays();
        PriceTemplateRespVO priceTemplate = null;
        if(templateId != null) {
            //有价格模板就生成航次价格
            priceTemplate = priceTemplateService.getPriceTemplate(templateId);
        }
        // 删除该日期内当前游轮当前航线的所有航次
        voyageMapper.deleteByShipIdAndRouteId(startDate, endDate.atTime(23, 59, 59), shipId, routeId);

        ShipRespDTO shipRespDTO = shipApi.queryShip(shipId);
        List<VoyageDO> voyageDOList = new ArrayList<>();
        while (!startDate.isAfter(endDate)) {
            VoyageDO voyageDO = new VoyageDO();
            Long id = IdUtil.getSnowflakeNextId();
            voyageDO.setId(id);
            // 离船日期
            LocalDate leaveDate = startDate.plusDays(voyageDays);
            String startDateStr = LocalDateTimeUtil.format(startDate, DatePattern.NORM_DATE_PATTERN);
            voyageDO.setName(shipRespDTO.getName()+startDateStr);
            voyageDO.setCode(VoyageUUCodeUtils.generateVoyageNo(shipRespDTO.getCode(), startDateStr, createReqVO.getBoardingAddress(), createReqVO.getLeaveAddress()));
            voyageDO.setShipId(shipId);
            voyageDO.setRouteId(routeId);
            voyageDO.setStartTime(LocalDateTimeUtil.offset(LocalDateTimeUtil.parse(startDateStr + "T" + startTime), startIntervalDays, ChronoUnit.DAYS));
            voyageDO.setLeaveTime(LocalDateTimeUtil.offset(LocalDateTimeUtil.parse(leaveDate + "T" + leaveTime), leaveIntervalDays, ChronoUnit.DAYS));
            voyageDO.setBoardingTime(LocalDateTimeUtil.parse(startDateStr+"T"+boardingTime));
            voyageDO.setType(createReqVO.getType());
            voyageDO.setTransfer(YesOrNoEnum.NO.getType());
            voyageDO.setTransferTime(null);
            voyageDO.setBoardingDockId(createReqVO.getBoardingDockId());
            voyageDO.setBoardingAddress(createReqVO.getBoardingAddress());
            voyageDO.setLeaveAddress(createReqVO.getLeaveAddress());
            voyageDO.setChannel(createReqVO.getChannel());
            voyageDO.setStockVisible(createReqVO.getStockVisible());
            voyageDO.setShelfStatus(createReqVO.getShelfStatus());
            voyageDO.setVenue(createReqVO.getVenue());
            voyageDO.setIntroduce(createReqVO.getIntroduce());
            voyageDO.setNotice(createReqVO.getNotice());
            voyageDO.setBoardingNotice(createReqVO.getBoardingNotice());
            voyageDO.setChildBookingNotice(createReqVO.getChildBookingNotice());
            voyageDO.setBookingNotice(createReqVO.getBookingNotice());
            voyageDOList.add(voyageDO);
            //有价格模板就生成航次价格
            if(priceTemplate != null) { 
                PriceVoyageSaveReqVO priceVoyageSaveReqVO = new PriceVoyageSaveReqVO();
                Long priceVoyageId = IdUtil.getSnowflakeNextId();
                priceVoyageSaveReqVO.setId(priceVoyageId);
                priceVoyageSaveReqVO.setShipId(shipId);
                priceVoyageSaveReqVO.setRouteId(routeId);
                priceVoyageSaveReqVO.setTemplateId(templateId);
                priceVoyageSaveReqVO.setVoyageId(id);
                priceVoyageSaveReqVO.setPriceMode(priceTemplate.getPriceMode());
                priceVoyageSaveReqVO.setValidateTime(priceTemplate.getValidateTime());
                priceVoyageSaveReqVO.setStatus(UseStatusEnum.ENABLE.getStatus());
                // 房型价格系数
                List<PriceRoomModelRespVO> roomModelList = priceTemplate.getRoomModelList();
                List<PriceRoomModelDO> priceRoomModelList = BeanUtils.toBean(roomModelList, PriceRoomModelDO.class);
                priceRoomModelList.forEach(priceRoomModelDO -> {
                    priceRoomModelDO.setObjectId(priceVoyageId);
                    List<PriceRoomModelTypeDO> roomModelTypeList = priceRoomModelDO.getRoomModelTypeList();
                    if(CollectionUtils.isAnyEmpty(roomModelTypeList)) {
                        return;
                    }
                    roomModelTypeList.forEach(roomModelType -> {
                        roomModelType.setObjectId(priceVoyageId);
                        PriceSingleSettingDO singleSetting = roomModelType.getSingleSetting();
                        if(singleSetting == null) {
                            return;
                        }
                        singleSetting.setObjectId(priceVoyageId);

                    });
                });
                priceVoyageSaveReqVO.setRoomModelList(priceRoomModelList);
                // 楼层加价
                List<PriceFloorRespVO> floorList = priceTemplate.getFloorList();
                List<PriceFloorDO> priceFloorList = BeanUtils.toBean(floorList, PriceFloorDO.class);
                priceFloorList.forEach(priceFloorDO -> priceFloorDO.setObjectId(priceVoyageId));
                priceVoyageSaveReqVO.setFloorList(priceFloorList);
                //区域加价
                List<PriceAreaRespVO> areaList = priceTemplate.getAreaList();
                List<PriceAreaDO> priceAreaList = BeanUtils.toBean(areaList, PriceAreaDO.class);
                priceAreaList.forEach(priceAreaDO -> priceAreaDO.setObjectId(priceVoyageId));
                priceVoyageSaveReqVO.setAreaList(priceAreaList);
                //区域基准价
                List<BasicPriceAreaRespVO> basicAreaList = priceTemplate.getBasicAreaList();
                List<BasicPriceAreaDO> basicPriceAreaList = BeanUtils.toBean(basicAreaList, BasicPriceAreaDO.class);
                basicPriceAreaList.forEach(basicPriceAreaDO -> basicPriceAreaDO.setObjectId(priceVoyageId));
                priceVoyageSaveReqVO.setBasicAreaList(basicPriceAreaList);
                // 附加产品
                List<PriceSpuRespVO> spuList = priceTemplate.getSpuList();
                List<PriceSpuDO> priceSpuList = BeanUtils.toBean(spuList, PriceSpuDO.class);
                priceSpuList.forEach(priceSpuDO -> priceSpuDO.setObjectId(priceVoyageId));
                priceVoyageSaveReqVO.setSpuList(priceSpuList);
                priceVoyageService.createPriceVoyage(priceVoyageSaveReqVO);
            }

            startDate = leaveDate.plusDays(intervalDays);
        }
        voyageMapper.insertBatch(voyageDOList);
        saveOrUpdateVoyageStockBatch(voyageDOList, shipId);
    }

    @Override
    public List<VoyageDO> getListCalendar(VoyageCalendarReqVO reqVO) {
        return voyageMapper.selectListCalendar2(reqVO);
    }

    @Override
    public List<VoyageDO> getListByShipId(Long shipId) {
        return voyageMapper.selectListByShipId(shipId);
    }

    @Override
    public List<AppVoyageDayRespVO> getVoyageListByShipIdAndDate(Long shipId, String date) {
        return voyageMapper.selectVoyageListByShipIdAndDate(shipId, date);
    }

    @Override
    public void updateLockVoyage(String id) {
        log.info("准备锁定或解锁航次: {}", id);
        VoyageDO voyageDO = voyageMapper.selectById(id);
        if (voyageDO == null) {
            log.info("未查询到航次: {}， 返回异常", id);
            throw exception(VOYAGE_NOT_EXISTS);
        }
        if(voyageDO.getIsLock() == null || voyageDO.getIsLock().intValue() == 0){
            log.info("航次: {} 当前锁定状态: 解锁， 将改为锁定 ...", id);
            voyageDO.setIsLock(1);
        } else {
            log.info("航次: {} 当前锁定状态: 锁定， 将改为解锁 ...", id);
            voyageDO.setIsLock(0);
        }
        voyageMapper.updateById(voyageDO);
        log.info("航次: {}, {}完成 ...", id, (voyageDO.getIsLock().intValue() == 0 ? "解锁" : "锁定"));
    }

    @Override
    public List<Map<String, Object>> countBatchByVoyageIds(List<Long> voyageIds) {
        return voyageMapper.countBatchByVoyageIds(voyageIds);
    }

}