package com.clx.performance.service.impl;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.clx.open.sdk.callback.OpenCallBackClient;
import com.clx.open.sdk.callback.message.MslExceptionReportDealMessage;
import com.clx.open.sdk.callback.message.SmbExceptionReportDealMessage;
import com.clx.open.sdk.enums.ExceptionReportEnum;
import com.clx.open.sdk.request.action.*;
import com.clx.performance.config.ThirdAppConfig;
import com.clx.performance.constant.RedisConstants;
import com.clx.performance.dao.OrderChildDao;
import com.clx.performance.dao.OrderChildExceptionReportDao;
import com.clx.performance.dao.OrderChildExceptionReportDealLogDao;
import com.clx.performance.dao.OrderChildExceptionReportOperationLogDao;
import com.clx.performance.dao.breakcontract.BreakContractDriverRecordDao;
import com.clx.performance.enums.*;
import com.clx.performance.model.OrderChild;
import com.clx.performance.model.OrderChildExceptionReport;
import com.clx.performance.model.OrderChildExceptionReportDealLog;
import com.clx.performance.model.OrderChildExceptionReportOperationLog;
import com.clx.performance.model.breakcontract.BreakContractDriverRecord;
import com.clx.performance.param.pc.DealExceptionReportParam;
import com.clx.performance.param.pc.PageExceptionReportParam;
import com.clx.performance.param.pc.TransferDriverBreakContractParam;
import com.clx.performance.service.OrderChildExceptionReportService;
import com.clx.performance.service.breakcontract.BreakContractDriverRecordService;
import com.clx.performance.struct.OrderChildExceptionReportOperationLogStruct;
import com.clx.performance.struct.OrderChildExceptionReportStruct;
import com.clx.performance.utils.LocalDateTimeUtils;
import com.clx.performance.utils.excel.ExcelData;
import com.clx.performance.utils.excel.ExcelField;
import com.clx.performance.utils.excel.ExcelSheet;
import com.clx.performance.utils.excel.ExcelUtil;
import com.clx.performance.vo.pc.OrderChildExceptionReportOperationLogVO;
import com.clx.performance.vo.pc.OrderChildExceptionReportVO;
import com.msl.common.base.Optional;
import com.msl.common.exception.ServiceSystemException;
import com.msl.common.result.Result;
import com.msl.common.utils.DateUtils;
import com.msl.user.data.UserSessionData;
import com.msl.user.utils.TokenUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author kavin
 * Date 2024-10-22
 * Time 10:38
 */
@Service
@Slf4j
@AllArgsConstructor
public class OrderChildExceptionReportServiceImpl  implements OrderChildExceptionReportService {

    private final OrderChildExceptionReportStruct orderChildExceptionReportStruct;
    private final OrderChildExceptionReportDao orderChildExceptionReportDao;
    private final OrderChildExceptionReportDealLogDao orderChildExceptionReportDealLogDao;
    private final RedisTemplate<String,Object> redisTemplate;
    private final ThirdAppConfig thirdAppConfig;
    private final OrderChildExceptionReportOperationLogDao orderChildExceptionReportOperationLogDao;
    private final BreakContractDriverRecordService breakContractDriverRecordService;
    private final OrderChildDao orderChildDao;
    private final BreakContractDriverRecordDao breakContractDriverRecordDao;
    private final OrderChildExceptionReportOperationLogStruct orderChildExceptionReportOperationLogStruct;

    @Override
    public Result<String> smbSyncExceptionReportInfo(SmbSyncExceptionReportInfoAction action) {
        OrderChildExceptionReport report = orderChildExceptionReportStruct.convertInfoSmbAction(action);
        report.setPictureUrlJson(JSON.toJSONString(action.getPictureUrl()));
        report.setVideoUrlJson(JSON.toJSONString(action.getVideoUrl()));
        report.setExceptionCategory(action.getOneLevelName());
        report.setExceptionType(action.getTwoLevelName() + action.getThreeLevelName());
        return Result.ok(saveExceptionReportInfo(report, SyncPlatformEnum.Source.TRADE_PLATFORM.getCode()));
    }

    @Override
    public Result<Object> smbSyncExceptionReportResult(SmbSyncExceptionReportResultAction action) {
        updateExceptionReportResult(SyncPlatformEnum.Source.TRADE_PLATFORM.getCode(),
                action.getThirdReportNo(),action.getBreakContractParty());
        return Result.ok();
    }

    @Override
    public Result<Object> smbWithdrawExceptionReport(SmbWithdrawExceptionReportAction action) {
        withdrawExceptionReport(SyncPlatformEnum.Source.TRADE_PLATFORM.getCode(), action.getThirdReportNo());
        return Result.ok();
    }

    @Override
    public Result<String> mslSyncExceptionReportInfo(MslSyncExceptionReportInfoAction action) {
        OrderChildExceptionReport report = orderChildExceptionReportStruct.convertInfoMslAction(action);
        report.setPictureUrlJson(JSON.toJSONString(action.getPictureUrl()));
        report.setVideoUrlJson(JSON.toJSONString(action.getVideoUrl()));
        return Result.ok(saveExceptionReportInfo(report, SyncPlatformEnum.Source.NEW_OWNER_CLIENT.getCode()));
    }

    @Override
    public Result<Object> mslSyncExceptionReportResult(MslSyncExceptionReportResultAction action) {
        updateExceptionReportResult(SyncPlatformEnum.Source.NEW_OWNER_CLIENT.getCode(),
                action.getThirdReportNo(),action.getBreakContractParty());
        return Result.ok();
    }

    @Override
    public Result<Object> mslWithdrawExceptionReport(MslWithdrawExceptionReportAction action) {
        withdrawExceptionReport(SyncPlatformEnum.Source.NEW_OWNER_CLIENT.getCode(), action.getThirdReportNo());
        return Result.ok();
    }

    public void updateExceptionReportResult(Integer source,String thirdReportNo,Integer breakContractParty){
        OrderChildExceptionReport report = orderChildExceptionReportDao.findByThirdReportNoAndSource(source,thirdReportNo);

        if(Objects.isNull(report)){
            log.warn("通过第三方上报编号：{} 未找到对应数据",thirdReportNo);
            throw new ServiceSystemException(ResultEnum.DATA_NOT_FIND);
        }
        Integer oldStatus = report.getStatus();
        if(Objects.equals(report.getStatus(), CarrierExceptionReportEnum.Status.PROCESSED.getCode())
                || Objects.equals(report.getStatus(), CarrierExceptionReportEnum.Status.WITHDRAWN.getCode())){
            throw new ServiceSystemException(ResultEnum.PARAM_ERROR,"异常数据已被处理或撤回");
        }
        report.setDealTime(LocalDateTime.now());
        report.setDealUser(SyncPlatformEnum.Source.getNameByCode(source));
        report.setStatus(CarrierExceptionReportEnum.Status.PROCESSED.getCode());
        report.setDealResult(breakContractParty);
        orderChildExceptionReportDao.updateEntityByKey(report);

        if(Objects.equals(oldStatus,CarrierExceptionReportEnum.Status.APPEAL_IN_PROGRESS.getCode())){
            saveOperationLog(report.getReportNo(),CarrierExceptionLogEnum.Type.APPEAL_DEAL,
                    ExceptionReportEnum.DealResult.getNameByCode(breakContractParty),null,SyncPlatformEnum.Source.getNameByCode(source));
        }else{
            saveOperationLog(report.getReportNo(),CarrierExceptionLogEnum.Type.TIMEOUT,
                    ExceptionReportEnum.DealResult.getNameByCode(breakContractParty),null,SyncPlatformEnum.Source.getNameByCode(source));
        }



    }

    public void withdrawExceptionReport(Integer source,String thirdReportNo){
        OrderChildExceptionReport report = orderChildExceptionReportDao.findByThirdReportNoAndSource(source,thirdReportNo);
        if(Objects.isNull(report)){
            log.warn("通过第三方上报编号：{} 未找到对应数据",thirdReportNo);
            throw new ServiceSystemException(ResultEnum.DATA_NOT_FIND);
        }
        if(!Objects.equals(report.getStatus(), CarrierExceptionReportEnum.Status.WAIT_DEAL.getCode())){
            throw new ServiceSystemException(ResultEnum.PARAM_ERROR,"非待处理数据无法撤回");
        }
        report.setDealTime(LocalDateTime.now());
        report.setDealUser(SyncPlatformEnum.Source.getNameByCode(source));
        report.setStatus(CarrierExceptionReportEnum.Status.WITHDRAWN.getCode());
        orderChildExceptionReportDao.updateEntityByKey(report);
    }



    private String saveExceptionReportInfo(OrderChildExceptionReport report,Integer source){
        report.setReportNo(getReportNo());
        report.setSource(source);
        report.setStatus(CarrierExceptionReportEnum.Status.WAIT_DEAL.getCode());
        orderChildExceptionReportDao.saveEntity(report);
        saveOperationLog(report.getReportNo(),CarrierExceptionLogEnum.Type.CREATE,null,null,SyncPlatformEnum.Source.getNameByCode(source));
        return report.getReportNo();
    }


    public String getReportNo(){
        LocalDateTime begin = LocalDateTime.now();
        String datetime = DateUtils.formatDateTime(begin,"yyyyMMdd").get();
        String reportCacheKey = getReportCacheKey(datetime);
        int randomNumber = (int) (Math.random() * 99999999) + 10000000;

        boolean isFirst = false;

        if (Boolean.TRUE.equals(redisTemplate.hasKey(reportCacheKey))){
            List<Object> range = redisTemplate.opsForList().range(reportCacheKey, 0, -1);
            if(CollectionUtils.isNotEmpty(range)){
                //如果缓存中包含生成的数字，则重新获取
                while (range.contains(randomNumber)){
                    randomNumber = (int) (Math.random() * 99999999) + 10000000;
                }
                redisTemplate.opsForList().leftPush(reportCacheKey,randomNumber);
            }else{
                isFirst = true;
            }
        }else{   //首次
            isFirst = true;
        }
        if(isFirst){
            redisTemplate.opsForList().leftPush(reportCacheKey,randomNumber);
            LocalDateTime end = LocalDateTimeUtils.getDayEnd(begin);
            long seconds = LocalDateTimeUtils.betweenSecond(begin, end);
            redisTemplate.expire(reportCacheKey,seconds, TimeUnit.SECONDS);
        }
        return "YCSB" + datetime + randomNumber;

    }

    public String getReportCacheKey(String datetime){
        return RedisConstants.EXCEPTION_REPORT_CACHE_KEY + datetime;
    }

    @Override
    public void updateDealExceptionReport(DealExceptionReportParam param) {
        OrderChildExceptionReport report = orderChildExceptionReportDao.getEntityByKey(param.getId()).orElseThrow(
                ResultEnum.DATA_NOT_FIND);

        OrderChildExceptionReportDealLog dealLog =  null;

        if(Objects.equals(report.getStatus(), CarrierExceptionReportEnum.Status.PROCESSED.getCode())
                || Objects.equals(report.getStatus(), CarrierExceptionReportEnum.Status.WITHDRAWN.getCode())){
            throw new ServiceSystemException(ResultEnum.PARAM_ERROR,"异常数据已被处理或撤回");
        }
        if(Objects.equals(param.getDealWay(),ExceptionReportEnum.DealWay.APPEAL.getCode())  && StringUtils.isBlank(param.getAppealReason())){
            throw new ServiceSystemException(ResultEnum.PARAM_ERROR,"申诉原因不能为空");
        }
        UserSessionData loginUserInfo = TokenUtil.getLoginUserInfo();

        if(Objects.equals(param.getDealWay(),ExceptionReportEnum.DealWay.APPEAL.getCode())){
            report.setStatus(CarrierExceptionReportEnum.Status.APPEAL_IN_PROGRESS.getCode());
            dealLog = new OrderChildExceptionReportDealLog();
            dealLog.setReportNo(report.getReportNo());
            dealLog.setDealWay(param.getDealWay());
            dealLog.setAppealReason(param.getAppealReason());
            dealLog.setPictureUrl(JSON.toJSONString(param.getPictureUrl()));
            dealLog.setVideoUrl(JSON.toJSONString(param.getVideoUrl()));

        }else{
            report.setStatus(CarrierExceptionReportEnum.Status.PROCESSED.getCode());
            report.setDealTime(LocalDateTime.now());
            report.setDealResult(ExceptionReportEnum.DealResult.CARRIER_BREAK_CONTRACT.getCode());
            report.setDealUserCode(loginUserInfo.getUserNo());
            report.setDealUser(loginUserInfo.getUserName());

        }

        //同步给第三方
        if(Objects.equals(report.getSource(),SyncPlatformEnum.Source.TRADE_PLATFORM.getCode())){
            SmbExceptionReportDealMessage message = new SmbExceptionReportDealMessage();
            message.setThirdReportNo(report.getThirdReportNo());
            message.setDealWay(param.getDealWay());
            message.setAppealReason(param.getAppealReason());
            message.setPictureUrl(param.getPictureUrl());
            message.setVideoUrl(param.getVideoUrl());
            String data = JSONUtil.parse(message).toString();
            log.info("开始通知：{} 上报异常处理，请求参数：{}",SyncPlatformEnum.Source.TRADE_PLATFORM.getName(),data);
            OpenCallBackClient openCallBackClient = thirdAppConfig.getOpenCallBackClient(report.getSource().toString());
            Result<?> result = openCallBackClient.encryptPost(JSONUtil.parse(message).toString(),message.topic());
            log.info("结束通知：{} 上报异常处理，请求参数：{}",SyncPlatformEnum.Source.TRADE_PLATFORM.getName(),result);

        }

        if(Objects.equals(report.getSource(),SyncPlatformEnum.Source.NEW_OWNER_CLIENT.getCode())){
            MslExceptionReportDealMessage message = new MslExceptionReportDealMessage();
            message.setThirdReportNo(report.getThirdReportNo());
            message.setDealWay(param.getDealWay());
            message.setAppealReason(param.getAppealReason());
            message.setPictureUrl(param.getPictureUrl());
            message.setVideoUrl(param.getVideoUrl());
            String data = JSONUtil.parse(message).toString();
            log.info("开始通知：{} 上报异常处理，请求参数：{}",SyncPlatformEnum.Source.NEW_OWNER_CLIENT.getName(),data);
            OpenCallBackClient openCallBackClient = thirdAppConfig.getOpenCallBackClient(report.getSource().toString());
            Result<?> result = openCallBackClient.encryptPost(data,message.topic());
            log.info("结束通知：{} 上报异常处理，请求参数：{}",SyncPlatformEnum.Source.NEW_OWNER_CLIENT.getName(),result);

        }
        CarrierExceptionLogEnum.Type type = CarrierExceptionLogEnum.Type.AGREE;
        String result = ExceptionReportEnum.DealResult.CARRIER_BREAK_CONTRACT.getName();
        orderChildExceptionReportDao.updateEntityByKey(report);
        if(Objects.nonNull(dealLog)){
            orderChildExceptionReportDealLogDao.saveEntity(dealLog);
            type = CarrierExceptionLogEnum.Type.APPEAL;
            result = null;
        }
        saveOperationLog(report.getReportNo(),type,result,loginUserInfo.getUserNo(),loginUserInfo.getUserName());
    }

    @Override
    public Page<OrderChildExceptionReportVO> pageExceptionReport(PageExceptionReportParam param) {
        IPage<OrderChildExceptionReport> page  = orderChildExceptionReportDao.pageExceptionReport(param);
        return orderChildExceptionReportStruct.convertPage(page);
    }

    @Override
    public SXSSFWorkbook exportExceptionReport(PageExceptionReportParam param) {
        param.setPage(1);
        param.setPageSize(1000000);
        Page<OrderChildExceptionReportVO> page = pageExceptionReport(param);

        List<OrderChildExceptionReportVO> list = page.getRecords();

        // 组装表头
        List<ExcelField> fieldList = new ArrayList<>();
        fieldList.add(new ExcelField(0, "异常上报编号", "reportNo", 5000));
        fieldList.add(new ExcelField(1, "三方异常上报编号", "thirdReportNo", 5000));
        fieldList.add(new ExcelField(2, "来源", "sourceMsg", 5000));
        fieldList.add(new ExcelField(3, "异常类别", "exceptionCategory", 5000));
        fieldList.add(new ExcelField(4, "异常类型", "exceptionType", 5000));

        fieldList.add(new ExcelField(5, "车牌号/手机号", "truckNoOrMobile", 5000));
        fieldList.add(new ExcelField(6, "运单编号", "childNo", 5000));
        fieldList.add(new ExcelField(7, "状态", "statusMsg", 5000));
        fieldList.add(new ExcelField(8, "扣罚金额", "deductionAmount", 5000));
        fieldList.add(new ExcelField(9, "异常上报时间", "reportTime", 5000));

        fieldList.add(new ExcelField(10, "创建时间", "createTime", 5000));
        fieldList.add(new ExcelField(11, "处理人", "dealUser", 5000));
        fieldList.add(new ExcelField(11, "处理时间", "dealTime", 5000));
        fieldList.add(new ExcelField(11, "处理结果", "dealResult", 5000));
        fieldList.add(new ExcelField(11, "是否转司机违约", "transferDriverBreakContractMsg", 5000));

        // 组装数据
        List<List<ExcelData>> dataList = new ArrayList<>();
        for (OrderChildExceptionReportVO vo : list) {
            List<ExcelData> rowData = new ArrayList<>();
            rowData.add(new ExcelData(vo.getReportNo(),"-"));
            rowData.add(new ExcelData(vo.getThirdReportNo(),"-"));
            rowData.add(new ExcelData(vo.getSourceMsg(), "-"));
            rowData.add(new ExcelData(vo.getExceptionCategory(),"-"));
            rowData.add(new ExcelData(vo.getExceptionType(),"-"));
            rowData.add(new ExcelData(vo.getTruckNoOrMobile(),"-"));
            rowData.add(new ExcelData(vo.getChildNo(),"-"));
            rowData.add(new ExcelData(vo.getStatusMsg(),"-"));
            rowData.add(new ExcelData(Objects.nonNull(vo.getDeductionAmount())?vo.getDeductionAmount().movePointLeft(2):null,"-"));
            rowData.add(new ExcelData(vo.getReportTime(),"-"));
            rowData.add(new ExcelData(vo.getCreateTime(),"-"));
            rowData.add(new ExcelData(vo.getDealUser(),"-"));
            rowData.add(new ExcelData(vo.getDealTime(),"-"));
            rowData.add(new ExcelData(vo.getDealResultMsg(),"-"));
            rowData.add(new ExcelData(vo.getTransferDriverBreakContractMsg(),"-"));
            dataList.add(rowData);
        }
        ExcelSheet excelSheet = new ExcelSheet("异常上报处理", "异常上报处理", fieldList, dataList);

        //创建excel
        return ExcelUtil.create(excelSheet);
    }

    @Override
    public OrderChildExceptionReportVO getExceptionReport(Integer id) {
        OrderChildExceptionReport report = orderChildExceptionReportDao.getEntityByKey(id).orElseThrow(
                ResultEnum.DATA_NOT_FIND);
        OrderChildExceptionReportVO vo = orderChildExceptionReportStruct.convert(report);
        vo.setVideoUrlList(JSON.parseArray(report.getVideoUrlJson(),String.class));
        vo.setPictureUrlList(JSON.parseArray(report.getPictureUrlJson(),String.class));

        Optional<OrderChildExceptionReportDealLog> limitOneByField = orderChildExceptionReportDealLogDao.getLimitOneByField(
                OrderChildExceptionReportDealLog::getReportNo, report.getReportNo());
        if(limitOneByField.isPresent()) {
            OrderChildExceptionReportDealLog dealLog = limitOneByField.get();
            vo.setAppealPictureUrlList(JSON.parseArray(dealLog.getPictureUrl(),String.class));
            vo.setAppealVideoUrlList(JSON.parseArray(dealLog.getVideoUrl(),String.class));
        }
        List<OrderChildExceptionReportOperationLog> list = orderChildExceptionReportOperationLogDao.listByField(
                OrderChildExceptionReportOperationLog::getReportNo, report.getReportNo());
        List<OrderChildExceptionReportOperationLogVO> logs = orderChildExceptionReportOperationLogStruct.convertList(list);
        vo.setOperationLogs(logs);
        return vo;
    }

    @Override
    public void transferDriverBreakContract(TransferDriverBreakContractParam param) {
        OrderChildExceptionReport report = orderChildExceptionReportDao.getEntityByKey(param.getId()).orElseThrow(
                ResultEnum.DATA_NOT_FIND);

        if(Objects.equals(report.getStatus(),CarrierExceptionReportEnum.Status.PROCESSED.getCode())
            && Objects.equals(report.getDealResult(),ExceptionReportEnum.DealResult.CARRIER_BREAK_CONTRACT.getCode())){
            throw new ServiceSystemException(ResultEnum.DATA_ERROR);
        }
        //创建司机违约记录
        saveDriverBreakContractRecord(report);

        UserSessionData loginUserInfo = TokenUtil.getLoginUserInfo();
        report.setTransferDriverBreakContract(1);
        report.setBreakContractReason(param.getReason());
        orderChildExceptionReportDao.updateEntityByKey(report);
        saveOperationLog(report.getReportNo(),CarrierExceptionLogEnum.Type.TRANSFER_DRIVER_BREAK_CONTRACT,null,loginUserInfo.getUserNo(),
                loginUserInfo.getUserName());
    }

    public void saveOperationLog(String reportNo, CarrierExceptionLogEnum.Type type,String result,Long createBy,String createName){
        OrderChildExceptionReportOperationLog operationLog = new OrderChildExceptionReportOperationLog();
        operationLog.setReportNo(reportNo);
        operationLog.setType(type.getCode());
        operationLog.setResult(result);
        operationLog.setCreateBy(createBy);
        operationLog.setCreateName(createName);
        orderChildExceptionReportOperationLogDao.saveEntity(operationLog);
    }

    public void saveDriverBreakContractRecord(OrderChildExceptionReport report){
        UserSessionData loginUserInfo = TokenUtil.getLoginUserInfo();
        Long userNo = loginUserInfo.getUserNo();
        LocalDateTime time = LocalDateTime.now();
        String breakNo = breakContractDriverRecordService.breakNoGenerate();
        OrderChild orderChild = orderChildDao.getByChildNo(report.getChildNo()).orElseThrow(PerformanceResultEnum.ORDER_CHILD_NO_FOUND);
        BreakContractDriverRecord record = new BreakContractDriverRecord();
        record.setBreakNo(breakNo);
        record.setTriggerType(BreakContractRecordEnum.TriggerType.EXCEPTION_REPORT.getCode());
        record.setBreakContractPartyType(BreakContractRecordEnum.BreakContractPartyType.DRIVER.getCode());
        record.setChildNo(orderChild.getChildNo());
        record.setTruckNo(orderChild.getTruckNo());
        record.setTruckOwnName(orderChild.getTruckOwnName());
        record.setTruckOwnUserNo(orderChild.getTruckOwnUserNo());
        record.setDriverUserNo(orderChild.getDriverUserNo());
        record.setDriverName(orderChild.getDriverName());
        record.setFigure(report.getDeductionAmount());
        record.setExceptionCategory(record.getExceptionCategory());
        record.setExceptionType(record.getExceptionType());
        record.setExceptionReportNo(report.getReportNo());
        record.setPictureUrl(record.getPictureUrl());
        record.setVideoUrl(record.getVideoUrl());
        record.setRemark(report.getBreakContractReason());
        record.setCreateBy(userNo);
        record.setCreateName(loginUserInfo.getUserName());
        record.setCreateTime(time);
        breakContractDriverRecordDao.saveEntity(record);
    }
}
