package com.xinxincaravan.caravan.vehicle.biz;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.github.wxiaoqi.security.common.biz.BaseBiz;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.xinxincaravan.caravan.vehicle.common.CustomIllegalParamException;
import com.xinxincaravan.caravan.vehicle.common.RestResponse;
import com.xinxincaravan.caravan.vehicle.constant.RedisKey;
import com.xinxincaravan.caravan.vehicle.constant.ResCode.ResCode;
import com.xinxincaravan.caravan.vehicle.constant.VehicleBookRecordStatus;
import com.xinxincaravan.caravan.vehicle.entity.BookRecordAccItem;
import com.xinxincaravan.caravan.vehicle.entity.VehicleBookRecord;
import com.xinxincaravan.caravan.vehicle.mapper.BookRecordAccItemMapper;
import com.xinxincaravan.caravan.vehicle.mapper.VehicleBookRecordMapper;
import com.xinxincaravan.caravan.vehicle.vo.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class VehicleBookRecordBiz extends BaseBiz<VehicleBookRecordMapper, VehicleBookRecord> {

    public static final String TB_NAME_PREFIX = "vehicle_book_record_his_";
    public static final String TB_NAME_REAL = "vehicle_book_record";//实际表名
    public static final String TB_ACC_ITEM_NAME_PREFIX = "book_record_acc_item_his_";
    public static final String TB_ACC_ITEM_NAME_REAL = "book_record_acc_item";//实际表名
    public static final DateTimeFormatter YEAR_DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyy");
    public static final DateTimeFormatter YEARMONTH_DATE_TIME_FORMATTER = DateTimeFormat.forPattern("yyyy-MM");
    public static final Integer DEL_BATCH_SIZE = 1000;
    public static final Integer COPY_BATCH_SIZE = 100;

    @Autowired
    private RedisTemplate customRedisTemplate;
    @Autowired
    private BookRecordAccItemMapper bookRecordAccItemMapper;

    public QueryVehicleBookRecordVo getById(Long id,DateTime endDate) throws Exception{
        Map<String,Object> recordParams =Maps.newHashMap();
        recordParams.put("id",id);
        recordParams.put("tbName",TB_NAME_REAL);
        DateTime selectedMonthDate = endDate.withMillisOfDay(0).withDayOfMonth(1);
        String bookRecordAccItemTbName = TB_ACC_ITEM_NAME_REAL;
        if(selectedMonthDate.compareTo(DateTime.now().plusMonths(-1).withDayOfMonth(1).withMillisOfDay(0)) < 0){
            recordParams.put("tbName",getTbName(String.valueOf(selectedMonthDate.getYear())));
            bookRecordAccItemTbName = getAccItemTbName(String.valueOf(selectedMonthDate.getYear()));
        }
        VehicleBookRecord vehicleBookRecord = mapper.getById(recordParams);
        QueryVehicleBookRecordVo queryVehicleBookRecordVo = new QueryVehicleBookRecordVo();
        BeanUtils.copyProperties(queryVehicleBookRecordVo,vehicleBookRecord);
        Map<Long,Map<Integer,Integer>> rs = getRelatedAccItemsAndAmount(Lists.newArrayList(queryVehicleBookRecordVo),bookRecordAccItemTbName);
        queryVehicleBookRecordVo.setAccItemAndAmount(rs.get(queryVehicleBookRecordVo.getId()));
        return queryVehicleBookRecordVo;
    }



    public RestResponse<Integer> lift(Integer operatorId,String userName,LiftVehicleVo liftVehicleVo) throws Exception{
        VehicleBookRecord record=mapper.selectByPrimaryKey( liftVehicleVo.getId());
        Integer mileageLift=liftVehicleVo.getMileageLift();
        if(record==null||mileageLift==null){
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_MILEAGE_CHANGED.getCode());
        }

        Integer mileageLift1=record.getMileageLift();
        if(mileageLift1==null||mileageLift>=mileageLift1){
        Map<String, Object> params = PropertyUtils.describe(liftVehicleVo);
        params.put("reviewerLift", operatorId);
        params.put("reviewerNameLift", userName);
        params.put("targetStatus", VehicleBookRecordStatus.LIFTED.getCode());
        params.put("conditionStatus", VehicleBookRecordStatus.APPROVE.getCode());
        params.put("mileageLift",mileageLift );
        Integer effected = mapper.liftOrRet(params);
        if(effected == 0){
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_STATUS_CHANGED.getCode());
        }
        }else {
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_MILEAGE_CHANGED.getCode());
        }
        return RestResponse.suc();
    }

    public RestResponse<Integer> retVehicle(Integer operatorId,String userName,RetVehicleVo retVehicleVo) throws Exception{
        VehicleBookRecord record=mapper.selectByPrimaryKey( retVehicleVo.getId());
        Integer mileageRet=retVehicleVo.getMileageRet();
        if(record==null||mileageRet==null){
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_MILEAGE_CHANGED.getCode());
        }

        Integer mileageLift=record.getMileageLift();
        if(mileageLift!=null&&mileageRet>=mileageLift){
        Map<String, Object> params = PropertyUtils.describe(retVehicleVo);
        params.put("targetStatus", VehicleBookRecordStatus.RETURNED.getCode());
        params.put("conditionStatus", VehicleBookRecordStatus.LIFTED.getCode());
        params.put("reviewerReturn", operatorId);
        params.put("reviewerNameReturn", userName);
        params.put("mileageRet", mileageRet);
        params.put("haveViolation", retVehicleVo.getHaveViolation());
        Integer effected = mapper.liftOrRet(params);
        if(effected == 0){
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_STATUS_CHANGED.getCode());
        }
        }else {
            return RestResponse.code(ResCode.VEHICLE_BOOKED_RECORD_MILEAGE_CHANGED.getCode());
        }
        return RestResponse.suc();
    }

    /**
     * 按页查询
     * @param vehicleBookRecordQueryVo
     * @return
     * @throws Exception
     */
    public PageDataVo<QueryVehicleBookRecordVo> page(VehicleBookRecordQueryVo vehicleBookRecordQueryVo,List<Integer> companyList) throws Exception{
        Map<String, Object> params = PropertyUtils.describe(vehicleBookRecordQueryVo);
        String selectedMonth = (String)params.get("selectedMonth");
        if(StringUtils.isBlank(selectedMonth)){
            throw new CustomIllegalParamException(" no month selected ");
        }
        params.put("tbName",TB_NAME_REAL);
        DateTime selectedMonthDate = DateTime.parse(selectedMonth,YEARMONTH_DATE_TIME_FORMATTER);
        String bookRecordAccItemTbName = TB_ACC_ITEM_NAME_REAL;
        if(selectedMonthDate.compareTo(DateTime.now().plusMonths(-1).withDayOfMonth(1).withMillisOfDay(0)) < 0){
            params.put("tbName",getTbName(String.valueOf(selectedMonthDate.getYear())));
            bookRecordAccItemTbName = getAccItemTbName(String.valueOf(selectedMonthDate.getYear()));
        }
        params.put("bookedStartDate",selectedMonthDate.withDayOfMonth(1).toString());
        params.put("bookedEndDate",selectedMonthDate.plusMonths(1).withDayOfMonth(1).toDate());
        Integer pageSize = (Integer) params.get("limit");
        params.remove("pageSize");
        Integer pageNo = (Integer) params.get("page");
        params.remove("pageNo");
        PageHelper.startPage(pageNo,pageSize);
        params.put("companyList", companyList);
        List<QueryVehicleBookRecordVo> bookRecordAndVehicleInfo = mapper.getByPage(params);
        Map<Long,Map<Integer,Integer>> rs = getRelatedAccItemsAndAmount(bookRecordAndVehicleInfo,
                bookRecordAccItemTbName);
        //获取相关随行物品信息
        for(QueryVehicleBookRecordVo queryVehicleBookRecordVo:bookRecordAndVehicleInfo){
            queryVehicleBookRecordVo.setAccItemAndAmount(rs.get(queryVehicleBookRecordVo.getId()));
        }
        PageInfo<QueryVehicleBookRecordVo> vehiclePageInfo = new PageInfo<>(bookRecordAndVehicleInfo);
        return PageDataVo.pageInfo(vehiclePageInfo);
    }

    /**
     * 获取相关申请记录对应随行物品的数量，并放入map
     * @param bookRecordAndVehicleInfos
     * @return
     */
    public Map<Long,Map<Integer,Integer>> getRelatedAccItemsAndAmount(List<QueryVehicleBookRecordVo> bookRecordAndVehicleInfos,
                                                                      String tbName){
        List<Long> bookRecordIds = Lists.newArrayList();
        if(CollectionUtils.isEmpty(bookRecordAndVehicleInfos)){
            return null;
        }
        for(QueryVehicleBookRecordVo queryVehicleBookRecordVo:bookRecordAndVehicleInfos){
            bookRecordIds.add(queryVehicleBookRecordVo.getId());
        }


        List<BookRecordAccItem> bookRecordAccItems = bookRecordAccItemMapper.getByRecords(ImmutableMap.of("bookRecordIds",bookRecordIds,
                "tbName",tbName));
        Map<Long,Map<Integer,Integer>> rs = Maps.newHashMap();
        for(BookRecordAccItem bookRecordAccItem : bookRecordAccItems){
            if(!rs.containsKey(bookRecordAccItem.getBookRecordId())){
                rs.put(bookRecordAccItem.getBookRecordId(),Maps.newHashMap());
            }
            rs.get(bookRecordAccItem.getBookRecordId()).
                    put(bookRecordAccItem.getAccItemId(),bookRecordAccItem.getAmount());
        }
        return rs;
    }


    /**
     * 迁移数据到历史表
     * 每年一张表
     */
    @Scheduled(cron = "0 0 1 1 * ?")//每月1号1点触发
    public void transfer2HistoryTb(){
        try {
            //获取表格名称
            DateTime now = DateTime.now();//当前获取的时间为标准
            log.info("开始预定记录迁移至历史表的定时任务。");
            Boolean needRun = copyDataLastMoth(now);
            if (needRun) {
                //每月初将上上月数据从当前信息表中删除
                delDataTheMonthBeforeLast(now);
            }
        }catch(Exception e){
            log.error(e.getMessage());
        }

    }


    /**
     * 获取上月数据，并复制到历史表
     */
    @Transactional
    public Boolean copyDataLastMoth(DateTime now) throws Exception{
        String lastMonthStr = now.plusMonths(-1).toString(YEARMONTH_DATE_TIME_FORMATTER);
        String redisKey = RedisKey.TRANSFER_BOOK_RECORD_LOCK_PREFIX +lastMonthStr;
        String tbName = getTbName(now.plusMonths(-1).toString("yyyy"));
        String accItemTbName = getAccItemTbName(now.plusMonths(-1).toString("yyyy"));
        Boolean hasSuc = customRedisTemplate.opsForValue().setIfAbsent(redisKey,String.valueOf(DateTime.now().getMillis()));
        if(hasSuc){//设置1天后过期
            customRedisTemplate.expire(redisKey,1, TimeUnit.DAYS);
        }else{
            log.info("[预定记录迁移]乐观锁获取失败，该线程不执行任务。");
            return Boolean.FALSE;
        }
        createTbIfNotExists(tbName);
        createAccItemTbIfNotExists(accItemTbName);
        //逐页查出数据
        Integer curPageNo = 1;
        List<VehicleBookRecord> vehicleBookRecords = null;
        do {
            Map<String, Object> params = Maps.newHashMap();
            params.put("startDate", now.plusMonths(-1).withMillisOfDay(0).toDate());
            params.put("endDate", now.plusDays(-1).withMillisOfDay(0).toDate());
            params.put("pageStart", (curPageNo - 1) * COPY_BATCH_SIZE);
            params.put("pageSize", COPY_BATCH_SIZE);
            vehicleBookRecords = mapper.getByPage4Month(params);
            //复制相关随行物品关系
            copyAccItemRel2His(vehicleBookRecords, accItemTbName);
            if(CollectionUtils.isNotEmpty(vehicleBookRecords)){
                //插入数据到历史表
                for(VehicleBookRecord vehicleBookRecord : vehicleBookRecords){
                    VehicleBookRecordAndTbNameVo vehicleBookRecordAndTbNameVo = new VehicleBookRecordAndTbNameVo();
                    BeanUtils.copyProperties(vehicleBookRecordAndTbNameVo,vehicleBookRecord);
                    vehicleBookRecordAndTbNameVo.setTbName(tbName);
                    mapper.insertHis(vehicleBookRecordAndTbNameVo);
                }
            }
            curPageNo++;
            log.info("【复制上月预定记录至历史表中】，当前复制页【"+curPageNo+"】，页大小【"+COPY_BATCH_SIZE+"】");
        }while(CollectionUtils.isNotEmpty(vehicleBookRecords));
        log.info("复制上月预定记录至历史表中完成，总页数【"+(curPageNo-1)+"】");
        return Boolean.TRUE;
    }


    /**
     * 删除上上月数据
     */
    @Transactional
    public void delDataTheMonthBeforeLast(DateTime now){
        Integer effected = 0;
        Integer total = 0;
        Map<String, Object> params = Maps.newHashMap();
        params.put("startDate", now.plusMonths(-2).withMillisOfDay(0).toDate());
        params.put("endDate", now.plusMonths(-1).plusDays(-1).withMillisOfDay(0).toDate());
        params.put("batchSize", DEL_BATCH_SIZE);
        do {
            //删除相关随行物品数据
            List<Long> bookRecordIds = mapper.getIdInYearMothLimit(params);
            deleteAccItemRelByRecord(bookRecordIds);
            effected = mapper.del4YearMoth(params);
            total+=effected;
            log.info("开始删除预定记录数据，删除总数【"+total+"】");
        }while(effected!=0);
        log.info("删除预定记录数据完成");
    }

    /**
     * 删除随影订车记录的随行物品记录
     * @param bookRecordIds
     */
    private void deleteAccItemRelByRecord(List<Long> bookRecordIds){
        if(CollectionUtils.isEmpty(bookRecordIds)){
            return;
        }
        Example example = new Example(BookRecordAccItem.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andIn("bookRecordId",bookRecordIds);
        bookRecordAccItemMapper.deleteByExample(example);
    }

    /**
     * 复制相应预定记录的随行物品信息
     * （注意了参数列表中的记录的历史年份必须属于当前月）
     * @param vehicleBookRecords
     */
    @Transactional
    public void copyAccItemRel2His(List<VehicleBookRecord> vehicleBookRecords,String accItemTbName){
        if(CollectionUtils.isEmpty(vehicleBookRecords)){
            return ;
        }
        List<Long> recordIds = Lists.newArrayList();
        for(VehicleBookRecord vehicleBookRecord : vehicleBookRecords){
            recordIds.add(vehicleBookRecord.getId());
        }
        List<BookRecordAccItem> bookRecordAccItems = bookRecordAccItemMapper.getByRecords(
                ImmutableMap.of("bookRecordIds",recordIds,"tbName",TB_ACC_ITEM_NAME_REAL));
        if(CollectionUtils.isEmpty(bookRecordAccItems)){
            return;
        }
        List<Map<String,Object>> bookRecordAccItemsTemp = Lists.newArrayList();
        for(Integer index = 0;index <bookRecordAccItems.size();index ++){
            bookRecordAccItemsTemp.add(ImmutableMap.of(
                    "id",bookRecordAccItems.get(index).getId(),
                    "bookRecordId",bookRecordAccItems.get(index).getBookRecordId(),
                    "accItemId",bookRecordAccItems.get(index).getAccItemId(),
                    "amount",bookRecordAccItems.get(index).getAmount()
            ));
            if(index == COPY_BATCH_SIZE || index == bookRecordAccItems.size() -1){
                bookRecordAccItemMapper.batchAdd2Tb(ImmutableMap.of(
                        "list",bookRecordAccItemsTemp,
                        "tbName",accItemTbName
                        ));
                bookRecordAccItemsTemp = Lists.newArrayList();
            }
        }

    }

    private String getTbNameNow(){
        return TB_NAME_PREFIX+ DateTime.now().toString(YEAR_DATE_TIME_FORMATTER);
    }

    /**
     * 获取历史表名称
     * @param year
     * @return
     */
    private String getTbName(String year){
        return TB_NAME_PREFIX+ year;
    }

    private String getAccItemTbNameNow(){
        return TB_ACC_ITEM_NAME_PREFIX+ DateTime.now().toString(YEAR_DATE_TIME_FORMATTER);
    }

    /**
     * 获取历史表名称
     * @param year
     * @return
     */
    private String getAccItemTbName(String year){
        return TB_ACC_ITEM_NAME_PREFIX+ year;
    }

    /**
     * 创建当年相关表格
     */
    private void createTbIfNotExists(String tbName){
        mapper.createTbIfNotExists(tbName);
    }

    private void createAccItemTbIfNotExists(String tbName){
        bookRecordAccItemMapper.createTbIfNotExists(tbName);
    }


}
