package com.ningdatech.carapi.safe.manage;

import java.io.*;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.aop.framework.AopContext;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.ningdatech.basic.model.PageVo;
import com.ningdatech.basic.util.CollUtils;
import com.ningdatech.basic.util.NdDateUtils;
import com.ningdatech.basic.util.ValidUtil;
import com.ningdatech.carapi.car.vehicle.model.NdVehicleBaseInfo;
import com.ningdatech.carapi.common.aop.BuildChildrenRegionEntity;
import com.ningdatech.carapi.common.aop.BuildChildrenRegionWrapper;
import com.ningdatech.carapi.common.constant.CommonConstant;
import com.ningdatech.carapi.common.util.CodeUtil;
import com.ningdatech.carapi.common.util.ExcelDownUtil;
import com.ningdatech.carapi.common.util.ExcelExportStyle;
import com.ningdatech.carapi.datascope.aop.LambdaDataScope;
import com.ningdatech.carapi.datascope.aop.XmlDataScope;
import com.ningdatech.carapi.industry.model.dto.LabAcceptanceCertificateImportDto;
import com.ningdatech.carapi.safe.constant.VehicleAccidentReportEnum;
import com.ningdatech.carapi.safe.constant.VehicleAccidentReportStatusEnum;
import com.ningdatech.carapi.safe.entity.NdInsureInfo;
import com.ningdatech.carapi.safe.entity.NdInsureOutDanger;
import com.ningdatech.carapi.safe.entity.VehicleAccidentReport;
import com.ningdatech.carapi.safe.model.dto.InsurePageQuery;
import com.ningdatech.carapi.safe.model.dto.NdInsureOutDangerImportDTO;
import com.ningdatech.carapi.safe.model.po.NdInsureOutDangerAnalysisPO;
import com.ningdatech.carapi.safe.model.vo.NdInsureInfoVO;
import com.ningdatech.carapi.safe.model.vo.NdInsureOutDangerAnalysisVO;
import com.ningdatech.carapi.safe.model.vo.NdInsureOutDangerVO;
import com.ningdatech.carapi.safe.service.INdInsureInfoService;
import com.ningdatech.carapi.safe.service.INdInsureOutDangerAnalysisService;
import com.ningdatech.carapi.safe.service.INdInsureOutDangerService;
import com.ningdatech.carapi.safe.service.IVehicleAccidentReportService;
import com.ningdatech.carapi.sys.entity.Company;
import com.ningdatech.carapi.sys.entity.dto.CompanyDTO;
import com.ningdatech.carapi.sys.event.ImportDataEvent;
import com.ningdatech.carapi.sys.helper.CompaniesCacheHelper;
import com.ningdatech.carapi.sys.helper.VehiclesCacheHelper;
import com.ningdatech.carapi.sys.service.CompanyService;
import com.ningdatech.carapi.user.util.LoginUserUtil;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author PoffyZhang
 * @date 2022/10/19 上午11:18
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class VehicleInsureManage {

    private final INdInsureInfoService insureInfoService;
    private final INdInsureOutDangerService insureOutDangerService;
    private final INdInsureOutDangerAnalysisService insureOutDangerAnalysisService;
    private final CompaniesCacheHelper companiesCacheHelper;
    private final CompanyService companyService;
    private final IVehicleAccidentReportService vehicleAccidentReportService;
    private final VehiclesCacheHelper vehiclesCacheHelper;
    private final INdInsureOutDangerService outDangerService;


    public PageVo<NdInsureInfoVO> queryVehicleInsure(InsurePageQuery insurePageQuery) {
        //承保信息
        Page<NdInsureInfo> page = insurePageQuery.page();
        insureInfo(page, insurePageQuery);
        List<NdInsureInfoVO> data = CollUtils.convert(page.getRecords(), e -> {
            NdInsureInfoVO vo = BeanUtil.copyProperties(e, NdInsureInfoVO.class);
            CodeUtil.searchCompanyNameAndRegionName(vo);
            return vo;
        });
        return PageVo.of(data, page.getTotal());
    }

    public PageVo<NdInsureOutDangerVO> queryOutDanger(InsurePageQuery insurePageQuery) {
        ((VehicleInsureManage) AopContext.currentProxy()). queryOutDanger();
        //出险信息
        Page<NdInsureOutDanger> page = insurePageQuery.page();
        insureOutDanger(page, insurePageQuery);
        List<NdInsureOutDangerVO> data = CollUtils.convert(page.getRecords(), e -> {
            NdInsureOutDangerVO vo = BeanUtil.copyProperties(e, NdInsureOutDangerVO.class);
            CodeUtil.searchCompanyNameAndRegionName(vo);
            vo.setReportStatusName(VehicleAccidentReportStatusEnum.getDescByCode(e.getReportStatus()));
            return vo;
        });
        return PageVo.of(data, page.getTotal());
    }

    @Async()
    protected void queryOutDanger() {
        log.info("查询时车辆出险上报校对");
        //获取事故上报审核列表中通过审核的上报信息
        List<VehicleAccidentReport> reportList = vehicleAccidentReportService.list(Wrappers.lambdaQuery(VehicleAccidentReport.class)
                .select(VehicleAccidentReport::getCarPlate, VehicleAccidentReport::getAccidentTime)
                .eq(VehicleAccidentReport::getAuditStatus, VehicleAccidentReportEnum.AUDIT_APPROVED.getCode()));
        Map<String, Set<LocalDate>> carPlateMapDate = reportList.stream()
                .collect(Collectors.groupingBy(VehicleAccidentReport::getCarPlate,
                        Collectors.mapping(w -> w.getAccidentTime().toLocalDate(), Collectors.toSet())));
        //获取车辆出险信息表中的所有数据
        List<NdInsureOutDanger> records = insureOutDangerService.list(Wrappers.lambdaQuery(NdInsureOutDanger.class)
                .select(NdInsureOutDanger::getCarPlate, NdInsureOutDanger::getAccidentTime));
        //对比两个列表中的数据，更新车辆出险信息表中的上报状态
        List<Long> reported = records.stream().filter(w -> {
            Set<LocalDate> dates = carPlateMapDate.get(w.getCarPlate());
            if (CollUtil.isNotEmpty(dates)) {
                return dates.contains(w.getAccidentTime().toLocalDate());
            }
            return false;
        }).map(NdInsureOutDanger::getId).collect(Collectors.toList());
        if (!reported.isEmpty()) {
            LambdaUpdateWrapper<NdInsureOutDanger> update = Wrappers.lambdaUpdate(NdInsureOutDanger.class)
                    .set(NdInsureOutDanger::getReportStatus, VehicleAccidentReportStatusEnum.REPORTED.getCode())
                    .in(NdInsureOutDanger::getId, reported);
            insureOutDangerService.update(update);
        }
    }

    public PageVo<NdInsureOutDangerAnalysisVO> queryInsureOutDangerAnalysis(InsurePageQuery insurePageQuery) {
        //出险分析
        Page<NdInsureOutDangerAnalysisPO> page = insurePageQuery.page();
        analysis(page, insurePageQuery);
        List<NdInsureOutDangerAnalysisVO> data = CollUtils.convert(page.getRecords(),
                e -> {
                    NdInsureOutDangerAnalysisVO vo = BeanUtil.copyProperties(e, NdInsureOutDangerAnalysisVO.class);
                    CodeUtil.searchRegionName(vo);
                    return vo;
                });
        return PageVo.of(data, page.getTotal());
    }

    public void insureInfo(Page<NdInsureInfo> page, InsurePageQuery insurePageQuery) {
        LambdaQueryWrapper<NdInsureInfo> wrapper = Wrappers.lambdaQuery(NdInsureInfo.class);
        VehicleInsureManage clazz = (VehicleInsureManage) AopContext.currentProxy();
        clazz.searchInsureInfo(page, wrapper, insurePageQuery);
    }

    @LambdaDataScope(wrapperIndex = 1)
    @BuildChildrenRegionWrapper(entityIndex = 2, wrapperIndex = 1)
    public void searchInsureInfo(Page<NdInsureInfo> page, LambdaQueryWrapper<NdInsureInfo> wrapper, InsurePageQuery insurePageQuery) {
        wrapper.ge(Objects.nonNull(insurePageQuery.getQibaoStartTime()), NdInsureInfo::getInsureTimeStart, insurePageQuery.getQibaoStartTime())
                .le(Objects.nonNull(insurePageQuery.getQibaoEndTime()), NdInsureInfo::getInsureTimeStart, insurePageQuery.getQibaoEndTime())
                .ge(Objects.nonNull(insurePageQuery.getDaoqiStartTime()), NdInsureInfo::getInsureTimeEnd, insurePageQuery.getDaoqiStartTime())
                .le(Objects.nonNull(insurePageQuery.getDaoqiEndTime()), NdInsureInfo::getInsureTimeEnd, insurePageQuery.getDaoqiEndTime())
                .like(Objects.nonNull(insurePageQuery.getCarPlate()), NdInsureInfo::getCarPlate, insurePageQuery.getCarPlate())
                .like(StringUtils.isNotBlank(insurePageQuery.getCompanyName()), NdInsureInfo::getCompanyName, insurePageQuery.getCompanyName());
        wrapper.orderBy(Boolean.TRUE, Boolean.FALSE, NdInsureInfo::getUpdateOn);
        insureInfoService.page(page, wrapper);
    }

    public void insureOutDanger(Page<NdInsureOutDanger> page, InsurePageQuery insurePageQuery) {
        LambdaQueryWrapper<NdInsureOutDanger> wrapper = Wrappers.lambdaQuery(NdInsureOutDanger.class);
        VehicleInsureManage clazz = (VehicleInsureManage) AopContext.currentProxy();
        clazz.searchInsureOutDanger(page, wrapper, insurePageQuery);
    }

    @LambdaDataScope(wrapperIndex = 1)
    @BuildChildrenRegionWrapper(entityIndex = 2, wrapperIndex = 1)
    public void searchInsureOutDanger(Page<NdInsureOutDanger> page, LambdaQueryWrapper<NdInsureOutDanger> wrapper, InsurePageQuery insurePageQuery) {
        wrapper.like(Objects.nonNull(insurePageQuery.getCarPlate()), NdInsureOutDanger::getCarPlate, insurePageQuery.getCarPlate());
        wrapper.eq(Objects.nonNull(insurePageQuery.getReportStatus()),NdInsureOutDanger::getReportStatus,insurePageQuery.getReportStatus());
        wrapper.orderBy(Boolean.TRUE, Boolean.FALSE, NdInsureOutDanger::getUpdateOn);
        insureOutDangerService.page(page, wrapper);
    }

    public void analysis(Page<NdInsureOutDangerAnalysisPO> page, InsurePageQuery insurePageQuery) {
        VehicleInsureManage clazz = (VehicleInsureManage) AopContext.currentProxy();
        clazz.searchInsureAnalysis(page, insurePageQuery);
    }

    @XmlDataScope(entityIndex = 1)
    @BuildChildrenRegionEntity(entityIndex = 1)
    public void searchInsureAnalysis(Page<NdInsureOutDangerAnalysisPO> page, InsurePageQuery param) {
        insureOutDangerAnalysisService.pageAnalysis(page, param);
    }

    @EventListener(condition = "event.getTemplate() == T(com.ningdatech.carapi.sys.entity.enumeration.ImportTemplateEnum).INSURE_OUT_DANGER")
    public void importDataListener(ImportDataEvent event) throws IOException {
        MultipartFile file = (MultipartFile) event.getSource();
        EasyExcel.read(file.getInputStream(), LabAcceptanceCertificateImportDto.class,
                new ReadListener<NdInsureOutDangerImportDTO>() {

                    final List<NdInsureOutDangerImportDTO> insert = new ArrayList<>();

                    @Override
                    public void invoke(NdInsureOutDangerImportDTO data, AnalysisContext context) {
                        String s = ValidUtil.validFast(data);
                        Assert.isTrue(StrUtil.isBlank(s), s);
                        insert.add(data);
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                        log.info("所有数据读取完成:{}", insert.size());
                        importDataSave(insert);
                    }
                }).sheet(0).doRead();
    }

    private void importDataSave(List<NdInsureOutDangerImportDTO> records) {
        Long userId = LoginUserUtil.getUserId();
        LocalDateTime now = LocalDateTime.now();
        List<String> companyNames = CollUtils.fieldList(records, NdInsureOutDangerImportDTO::getCompanyName);
        List<Company> companies = companyService.listCompanyByCompanyNames(companyNames);
        Map<String, Company> companyMap = CollUtils.listToMap(companies, Company::getName);
        List<NdInsureOutDanger> list = new ArrayList<>();
        records.forEach(w -> {
            Company company = companyMap.get(w.getCompanyName());
            if (company == null) {
                return;
            }
            NdInsureOutDanger outDanger = new NdInsureOutDanger();
            outDanger.setAccidentAddress(w.getAccidentAddress());
            outDanger.setCompanyId(company.getId());
            outDanger.setCompanyName(company.getName());
            outDanger.setRegionId(company.getRegionId());
            outDanger.setRegionName(company.getRegionName());
            outDanger.setAccidentTime(w.getAccidentTime());
            outDanger.setInjuries(w.getInjuries());
            outDanger.setInsureCompany(w.getInsureCompany());
            outDanger.setCarPlate(w.getCarPlate());
            outDanger.setDeathToll(w.getDeathToll());
            outDanger.setCompensateAmount(w.getCompensateAmount());
            outDanger.setCreateBy(userId);
            outDanger.setCreateOn(now);
            outDanger.setUpdateBy(userId);
            outDanger.setUpdateOn(now);
            list.add(outDanger);
        });
        insureOutDangerService.saveBatch(list);
    }

    //==================================================================================================================

    public void exportOutDanger(HttpServletResponse response, InsurePageQuery insurePageQuery) {
        insurePageQuery.page(CommonConstant.EXPORT_PAGE_NUMBER, CommonConstant.EXPORT_PAGE_SIZE);
        Page<NdInsureOutDanger> page = insurePageQuery.page();
        insureOutDanger(page, insurePageQuery);
        List<NdInsureOutDangerVO> collect = page.getRecords().stream().map(w -> {
            NdInsureOutDangerVO vo = BeanUtil.copyProperties(w, NdInsureOutDangerVO.class);
            return vo;
        }).collect(Collectors.toList());
        String fileName = "出险信息";
        ExcelDownUtil.setFileName(fileName, response);
        // 数据导出处理函数
        try {
            EasyExcel.write(response.getOutputStream(), NdInsureOutDangerVO.class)
                    .autoCloseStream(false)
                    .registerWriteHandler(ExcelExportStyle.formalStyle())
                    .sheet(fileName)
                    .doWrite(collect);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void saveOrUpdate(NdInsureOutDangerVO req) {
        Long userId = LoginUserUtil.getUserId();
        LocalDateTime now = LocalDateTime.now();
        NdInsureOutDanger insert = BeanUtil.copyProperties(req, NdInsureOutDanger.class);
        insert.setUpdateBy(userId);
        insert.setUpdateOn(now);
        insert.setRegionId(companiesCacheHelper.getById(req.getCompanyId()).getRegionId());
        if (insert.getId() == null) {
            insert.setCreateBy(userId);
            insert.setUpdateOn(now);
        }
        insureOutDangerService.saveOrUpdate(insert);
    }

    public void importOutDanger() throws FileNotFoundException {

        // 获取所有出险数据信息
        Map<String, List<NdInsureOutDanger>> insureMap = insureOutDangerService.list().stream().collect(Collectors.groupingBy(NdInsureOutDanger::getCarPlate));
        File directory = new File("");
        // 临时文件存储路径
        String filePath = directory.getAbsolutePath() + File.separator + "outInsure";
        File file = new File(filePath + File.separator + "保险出险数据.xlsx");
        Workbook wb = readExcel(new FileInputStream(file),file.getName());
        Row row = null;
        if(wb != null){
            //获取第一个sheet
            Sheet sheet = wb.getSheetAt(0);
            //获取最大行数
            int rownum = sheet.getPhysicalNumberOfRows();
            //获取第一行
            row = sheet.getRow(0);
            //获取最大列数
            for (int i = 1; i<rownum; i++) {
                row = sheet.getRow(i);

                String carPlate = Objects.nonNull(row.getCell(0)) ? row.getCell(0).toString() : StringUtils.EMPTY;
                String driverName = Objects.nonNull(row.getCell(1)) ? row.getCell(1).toString() : StringUtils.EMPTY;
                String vehicleTypeName = Objects.nonNull(row.getCell(2)) ? row.getCell(2).toString() : StringUtils.EMPTY;
                String cityName = Objects.nonNull(row.getCell(3)) ? row.getCell(3).toString() : StringUtils.EMPTY;
                //区
                String regionName = Objects.nonNull(row.getCell(4)) ? row.getCell(4).toString() : StringUtils.EMPTY;
                String companyName = Objects.nonNull(row.getCell(5)) ? row.getCell(5).toString() : StringUtils.EMPTY;
                Date outDangerTime = Objects.nonNull(row.getCell(6)) ? row.getCell(6).getDateCellValue() : null;
                String address = Objects.nonNull(row.getCell(7)) ? row.getCell(7).toString() : StringUtils.EMPTY;
                //受伤人数
                String injuies = Objects.nonNull(row.getCell(8)) ? row.getCell(8).toString() : "0";
                //死亡人数
                String deathToll = Objects.nonNull(row.getCell(9)) ? row.getCell(9).toString() : "0";
                //已决赔款
                BigDecimal compensateAmount = BigDecimal.valueOf(0);
                Cell cellAmount = row.getCell(10);
                if(Objects.nonNull(cellAmount)){
                    if(cellAmount.toString().contains("+")){
                        String[] amountArr = cellAmount.toString().split("\\+");
                        compensateAmount = BigDecimal.valueOf(Double.valueOf(amountArr[0])).add(BigDecimal.valueOf(Double.valueOf(amountArr[1])));
                    }else{
                        compensateAmount = BigDecimal.valueOf(cellAmount.getNumericCellValue());
                    }
                }
                //保险公司
                String insureCompany = Objects.nonNull(row.getCell(13)) ? row.getCell(13).toString() : StringUtils.EMPTY;

                NdVehicleBaseInfo car = vehiclesCacheHelper.getByCarPlate(carPlate);
                if (Objects.isNull(car)){
                    log.info("{}不在系统里 ", carPlate);
                    continue;
                }

                // 根据事故时间和事故地点判断该出险数据是否已经存在
                List<NdInsureOutDanger> outDangers = insureMap.get(carPlate);
                List<NdInsureOutDanger> dangers = Lists.newArrayList();
                if (CollUtil.isNotEmpty(outDangers)) {
                    // 去重
                    HashSet<String> hashSet = new HashSet<>();
                    dangers = outDangers.stream()
                            .filter(c -> hashSet.add(c.getAccidentTime() + c.getAccidentAddress()))
                            .collect(Collectors.toList());
                }
                if (CollUtil.isNotEmpty(dangers)) {
                    Map<String, NdInsureOutDanger> dangerMap = dangers.stream()
                            .collect(Collectors.toMap(w -> w.getAccidentTime() + w.getAccidentAddress(), w -> w));
                    LocalDateTime accidentTime = NdDateUtils.date2LocalDateTime(outDangerTime);
                    String key = accidentTime + address;
                    // 只插入新增的
                    if (!dangerMap.containsKey(key)) {
                        NdInsureOutDanger outDanger = new NdInsureOutDanger();
                        outDanger.setCarPlate(carPlate);
                        outDanger.setInsureCompany(insureCompany);
                        outDanger.setAccidentTime(accidentTime);
                       
                        outDanger.setCompanyId(car.getCompanyId());
                        CompanyDTO company = companiesCacheHelper.getById(car.getCompanyId());
                        outDanger.setCompanyName(Objects.nonNull(company) ? company.getName() : StringUtils.EMPTY);
                        outDanger.setRegionId(car.getRegionId());
                        
                        outDanger.setDeathToll(Double.valueOf(deathToll).intValue());
                        outDanger.setInjuries(Double.valueOf(injuies).intValue());
                        outDanger.setCompensateAmount(compensateAmount);
                        outDanger.setAccidentAddress(address);
                        outDangerService.save(outDanger);
                        log.info("准备插入 " + i + "条:" + JSON.toJSONString(outDanger));
                    }
                }
            }
        }
    }

    public static Workbook readExcel(InputStream is, String extString){
        Workbook wb = null;
        extString = extString.substring(extString.lastIndexOf("."));
        try {
            if(".xls".equals(extString)){
                return wb = new HSSFWorkbook(is);
            }else if(".xlsx".equals(extString)){
                return wb = new XSSFWorkbook(is);
            }else {
                return wb = null;
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return wb;
    }
}
