package com.yeejoin.amos.supervision.business.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ELMode;
import com.deepoove.poi.data.RowRenderData;
import com.deepoove.poi.data.style.Style;
import com.deepoove.poi.policy.DynamicTableRenderPolicy;
import com.deepoove.poi.policy.MiniTableRenderPolicy;
import com.deepoove.poi.util.TableTools;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.yeejoin.amos.boot.biz.common.utils.DateUtils;
import com.yeejoin.amos.supervision.business.dao.mapper.CheckReportMapper;
import com.yeejoin.amos.supervision.business.dao.mapper.PlanTaskMapper;
import com.yeejoin.amos.supervision.business.dto.CheckReportMapperDto;
import com.yeejoin.amos.supervision.business.feign.DangerFeignClient;
import com.yeejoin.amos.supervision.business.feign.JCSFeignClient;
import com.yeejoin.amos.supervision.business.service.intfc.ICheckReportService;
import com.yeejoin.amos.supervision.business.service.intfc.IPlanService;
import com.yeejoin.amos.supervision.common.enums.DangerCheckTypeLevelEnum;
import com.yeejoin.amos.supervision.common.enums.PlanCheckLevelEnum;
import com.yeejoin.amos.supervision.common.enums.PlanFrequencyEnum;
import com.yeejoin.amos.supervision.common.enums.PlanTaskFinishStatusEnum;
import com.yeejoin.amos.supervision.core.common.dto.CheckReportCompanyDto;
import com.yeejoin.amos.supervision.core.common.dto.CheckReportDangerDto;
import com.yeejoin.amos.supervision.core.common.dto.CheckReportDto;
import com.yeejoin.amos.supervision.core.common.dto.CheckReportParamDto;
import com.yeejoin.amos.supervision.core.common.dto.DangerDto;
import com.yeejoin.amos.supervision.dao.entity.CheckReport;
import com.yeejoin.amos.supervision.dao.entity.Plan;
import com.yeejoin.amos.supervision.dao.entity.PlanTask;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.typroject.tyboot.core.foundation.utils.ValidationUtil;
import org.typroject.tyboot.core.rdbms.service.BaseService;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.yeejoin.amos.boot.biz.common.utils.DateUtils.dateFormat;

//import com.deepoove.poi.policy.HackLoopTableRenderPolicy;

@Service("checkReportService")
public class CheckReportServiceImpl extends BaseService<CheckReportDto, CheckReport, CheckReportMapper> implements ICheckReportService {

    @Autowired
    IPlanService iPlanService;

    @Autowired
    CheckReportMapper checkReportMapper;

    @Autowired
    DangerFeignClient dangerFeignClient;

    @Autowired
    PlanTaskMapper planTaskMapper;

    @Autowired
    JCSFeignClient jcsFeignClient;

    private static String[] stringSplit(String str) {
        String[] strs = new String[0];
        if (!ValidationUtil.isEmpty(str)) {
            strs = str.split(",");
            return strs;
        }
        return strs;
    }

    /**
     * 生成检查报告
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void genCheckReport(PlanTask planTask) throws ParseException {
        Map taskMap = planTaskMapper.queryPlanTaskById(planTask.getId());

        // 所有任务执行完成才生成报告
        if (PlanTaskFinishStatusEnum.FINISHED.getValue() == (Integer) taskMap.get("finishStatus")) {
            Plan plan = iPlanService.queryPlanById(planTask.getPlanId());
            String reportName = getReportName(plan, planTask);
            CheckReportMapperDto checkReportMapperDto = checkReportMapper.queryByPlanTaskId(planTask.getId());
            if (!ValidationUtil.isEmpty(checkReportMapperDto)) {
                CheckReport checkReport = new CheckReport();
                BeanUtils.copyProperties(checkReportMapperDto, checkReport);
                checkReport.setName(reportName);
                String[] checkTime = stringSplit(checkReportMapperDto.getCheckTime());
                checkReport.setStartCheckDate(DateUtils.dateParse(checkTime[0], DateUtils.DATE_PATTERN));
                checkReport.setEndCheckDate(DateUtils.dateParse(checkTime[checkTime.length - 1], DateUtils.DATE_PATTERN));
                String[] dangerIds = stringSplit(checkReportMapperDto.getLatentDangerId());
                checkReport.setDangerCount(dangerIds.length);
                checkReport.setDangerIds(checkReportMapperDto.getLatentDangerId());

                if (DangerCheckTypeLevelEnum.DEPARTMENT.getCode().equals(plan.getCheckLevel())) {
                    checkReport.setPlanType(0);
                } else {
                    checkReport.setPlanType(1);
                }
                checkReport.setOrgCode(getParentOrgCode(plan.getOrgCode()));
                String[] problemPointIds = stringSplit(checkReportMapperDto.getProblemPointId());
                checkReport.setProblemCompanyCount(problemPointIds.length);

                // 获取复查信息
                getReviewInfo(checkReport);

                checkReportMapper.insert(checkReport);
            }
        }
    }

    @Override
    public IPage<CheckReportDto> pageList(Page page, CheckReportParamDto queryParam) {
        return this.baseMapper.selectPageList(page, queryParam);
    }

    @Override
    public CheckReportDto getDetailById(String checkReportId) {
        CheckReportDto checkReportDto = this.baseMapper.selectDetailById(Long.valueOf(checkReportId));
        if (!ValidationUtil.isEmpty(checkReportDto)) {
            // 获取本次检查单位信息
            List<CheckReportCompanyDto> companyDtoList =
                    this.baseMapper.getCheckReportCompanyList(checkReportDto.getPlanTaskId());
            if (!ValidationUtil.isEmpty(companyDtoList)) {
                List<String> companyIdList = Lists.transform(companyDtoList, CheckReportCompanyDto::getId);
                Map<String, Integer> companyDeptCountMap =
                        jcsFeignClient.getDeptCountByCompanyIds(companyIdList).getResult();
                companyDtoList.forEach(c -> {
                    c.setDeptCount(companyDeptCountMap.get(c.getId()));
                });
            }
            checkReportDto.setCheckCompanyList(companyDtoList);

            // 获取隐患信息
            Map<String, String> dangerParamMap = Maps.newHashMap();
            // 本次隐患id
            List<String> dangerIdList = Arrays.asList(checkReportDto.getDangerIds().split(","));
            // 复核隐患id
            List<String> reviewDangerIdList = Arrays.asList(checkReportDto.getReviewDangerIds().split(","));
            Set<String> allDangerIdSet = Sets.newHashSet();
            allDangerIdSet.addAll(dangerIdList);
            allDangerIdSet.addAll(reviewDangerIdList);
            dangerParamMap.put("dangerIds", Joiner.on(",").join(allDangerIdSet));
            List<DangerDto> dangerDtoList = dangerFeignClient.listAll(dangerParamMap).getResult();
            List<CheckReportDangerDto> dangerList = Lists.newArrayList();
            List<CheckReportDangerDto> reviewDangerList = Lists.newArrayList();
            if (!ValidationUtil.isEmpty(dangerDtoList)) {
                dangerDtoList.forEach(dangerDto -> {
                    CheckReportDangerDto checkReportDangerDto = new CheckReportDangerDto();
                    BeanUtils.copyProperties(dangerDto, checkReportDangerDto);
                    Map<String, String> bizInfo = dangerDto.getBizInfo();
                    checkReportDangerDto.setCompanyId(bizInfo.get("pointId"));
                    checkReportDangerDto.setCompanyName(bizInfo.get("pointName"));

                    if (dangerIdList.contains(dangerDto.getId().toString())) {
                        dangerList.add(checkReportDangerDto);
                    }
                    if (reviewDangerIdList.contains(dangerDto.getId().toString())) {
                        checkReportDangerDto.setReviewUser(dangerDto.getExecuteUserName());
                        reviewDangerList.add(checkReportDangerDto);
                    }
                });
            }

            checkReportDto.setCheckDangerList(dangerList);
            checkReportDto.setReviewDangerList(reviewDangerList);
        }
        return checkReportDto;
    }

    public static String getWeekDate(Date date) throws ParseException {
        String month = DateUtils.dateFormat(date, DateUtils.CHN_DATE_PATTERN_MONTH);
        String weekDateStr = DateUtils.getWeekBeginDate(date) + "-" + DateUtils.getWeekEndDate(date);
        return month + weekDateStr + "日";
    }

    public static String getMonthDate(Date date) {
        return DateUtils.getMonth(date) + "月份";
    }

    /**
     * 获取复查信息
     *
     * @param checkReport
     */
    void getReviewInfo(CheckReport checkReport) {

        String t1 = getLastReportDate(checkReport);
        String t2 = DateUtils.getDateNowString();

        Map<String, Object> param = new HashMap<>();
        param.put("dangerIds", checkReport.getDangerIds());
        List<DangerDto> dangerDtoList = dangerFeignClient.listAll(param).getResult();
        // 现场整改的隐患
        dangerDtoList =
                dangerDtoList.stream().filter(dangerDto -> "onSiteRectification".equals(dangerDto.getReformType())).collect(Collectors.toList());
        checkReport.setOnsiteReformCount(dangerDtoList.size());

        Map<String, List<CheckReportDangerDto>> result = dangerFeignClient.getReviewInfoList(checkReport.getOrgCode(), t1, t2).getResult();
        // 复查隐患
        List<CheckReportDangerDto> reviewDangerList = result.get("reviewDangerList");
        // 复查已整改隐患
        List<CheckReportDangerDto> reviewReformedList = result.get("reviewReformedList");
        Set<String> reviewDangerIdSet = Sets.newHashSet();
        reviewDangerIdSet.addAll(Lists.transform(reviewDangerList, CheckReportDangerDto::getId));
        reviewDangerIdSet.addAll(Lists.transform(reviewReformedList, CheckReportDangerDto::getId));
        // 保存本次复查隐患id
        checkReport.setReviewDangerIds(Joiner.on(",").join(reviewDangerIdSet));
//        List<CheckReportDangerDto> reviewReformingList = result.get("reviewReformingList");
        List<CheckReportDangerDto> remainingList = result.get("remainingList");

        checkReport.setReviewDangerCount(reviewDangerList.size());
        checkReport.setReviewReformedCount(reviewReformedList.size());
//        checkReport.setReviewReformingCount(reviewReformingList.size());
        checkReport.setRemainingDangerCount(remainingList.size());
    }

    /**
     * 获取上次报告的创建时间
     *
     * @param checkReport
     * @return
     */
    private String getLastReportDate(CheckReport checkReport) {
        // 根据计划类型获取上一次报告生成时间（T1）到本次报告生成时间（T2）之间的数据
        String t1 = DateUtils.getDateNowString();
        LambdaQueryWrapper<CheckReport> queryWrapper = new LambdaQueryWrapper<>();
        if (PlanCheckLevelEnum.DRAFT.getValue() == checkReport.getPlanType()) {
            // 单位级根据orgCode获取本单位上一次报告生成时间t1
            queryWrapper.eq(CheckReport::getOrgCode, checkReport.getOrgCode()).orderByDesc(CheckReport::getCreateDate);
            List<CheckReport> reportList = this.baseMapper.selectList(queryWrapper);
            if (!ValidationUtil.isEmpty(reportList)) {
                t1 = DateUtils.date2LongStr(reportList.get(0).getCreateDate());
            }
        } else {
            // 公司级根据类型获取上一次公司级报告生成时间t1
            queryWrapper.eq(CheckReport::getPlanType, checkReport.getPlanType()).orderByDesc(CheckReport::getCreateDate);
            List<CheckReport> reportList = this.baseMapper.selectList(queryWrapper);
            if (!ValidationUtil.isEmpty(reportList)) {
                t1 = DateUtils.date2LongStr(reportList.get(0).getCreateDate());
            }
        }
        return t1;
    }

    public static String getQuarterDate(Date date) {
        int month = DateUtils.getMonth(date);
        String quarter = DateUtils.getQuarterStr(month);
        return quarter + "季度";
    }

    public static <T> Consumer<T> foreachWithIndex(BiConsumer<T, Integer> consumer) {
        class Obj {
            int i;
        }
        Obj obj = new Obj();
        return t -> {
            int index = obj.i++;
            consumer.accept(t, index);
        };
    }

    @Override
    public void getCheckReportDocx(HttpServletResponse response, String reportId) throws ParseException,
            UnsupportedEncodingException {
        CheckReportDto report = this.getDetailById(reportId);
        report.setReportDate(getCheckReportDateStr(report));
        report.setNowDate(DateUtils.dateFormat(new Date(), DateUtils.CHN_DATE_PATTERN));
        if (!ValidationUtil.isEmpty(report)) {
//            String checkReportTemplatePath = this.getClass().getClassLoader().getResource("templates/check-report-template.docx").getFile();
            String fileName = report.getName() + ".docx";
            InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("templates/check-report-template.docx");

            CheckDangerTablePolicy checkDangerTablePolicy = new CheckDangerTablePolicy();
            ReviewDangerTablePolicy reviewDangerTablePolicy = new ReviewDangerTablePolicy();
            Configure.ConfigureBuilder configureBuilder = Configure.newBuilder();
            configureBuilder.setElMode(ELMode.SPEL_MODE).bind("checkDangerList", checkDangerTablePolicy).bind("reviewDangerList", reviewDangerTablePolicy).build();
//            XWPFTemplate template = XWPFTemplate.compile(checkReportTemplatePath, configureBuilder.build()).render(report);
            if (resourceAsStream != null) {
                XWPFTemplate template = XWPFTemplate.compile(resourceAsStream, configureBuilder.build()).render(report);
                response.setContentType("application/msword");
                response.setHeader("Content-Disposition",
                        "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));
                OutputStream out = null;
                BufferedOutputStream bos = null;
                try {
                    out = response.getOutputStream();
                    bos = new BufferedOutputStream(out);
                    template.write(bos);
                    bos.flush();
                    out.flush();
                } catch (IOException e) {
                    log.error("生成文档失败---------->");
                    e.printStackTrace();
                } finally {
                    try {
                        bos.close();
                        out.close();
                        template.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                log.error("获取模板失败---------->");
            }
        }
    }

    private String getCheckReportDateStr(CheckReportDto checkReportDto) throws ParseException {
        PlanFrequencyEnum frequencyEnum = PlanFrequencyEnum.getEnumByCode(checkReportDto.getPlanCheckFrequencyType());
        String reportDate;
        switch (frequencyEnum) {
            case WEEK:
                reportDate = getWeekDate(checkReportDto.getStartPlanTaskDate());
                break;
            case MONTH:
                reportDate = getMonthDate(checkReportDto.getStartPlanTaskDate());
                break;
            case QUARTER:
                reportDate = getQuarterDate(checkReportDto.getStartPlanTaskDate());
                break;
            default:
                throw new IllegalStateException("计划频次有误: " + frequencyEnum);
        }
        return reportDate;
    }

    /**
     * 获取检查报告名称
     *
     * @param plan
     * @return
     */
    private String getReportName(Plan plan, PlanTask planTask) throws ParseException {
        Date planTaskBeginDate = DateUtils.dateParse(planTask.getBeginTime(), DateUtils.DATE_PATTERN);
        String yearDateStr = dateFormat(planTaskBeginDate, DateUtils.CHN_DATE_PATTERN_YEAR);
        PlanFrequencyEnum frequencyEnum = Objects.requireNonNull(PlanFrequencyEnum.getEnumByCode(plan.getPlanType()));
        switch (frequencyEnum) {
            case WEEK:
                yearDateStr =
                        yearDateStr + getWeekDate(planTaskBeginDate) + frequencyEnum.getExtraInfo();
                break;
            case MONTH:
                yearDateStr = yearDateStr + DateUtils.getMonth(planTaskBeginDate) + frequencyEnum.getExtraInfo();
                break;
            case QUARTER:
                yearDateStr = yearDateStr + DateUtils.getQuarterStr(DateUtils.getMonth(planTaskBeginDate)) + frequencyEnum.getExtraInfo();
                break;
        }

        return plan.getName() + "--" + yearDateStr;
    }

    // 表格填充数据所在行数
    static int dangerListDataStartRow = 2; // 表头占两行
    public static class CheckDangerTablePolicy extends DynamicTableRenderPolicy {

        @Override
        public void render(XWPFTable table, Object o) {
            if (ValidationUtil.isEmpty(o)) {
                table.removeRow(dangerListDataStartRow);
                return;
            }
            List<CheckReportDangerDto> reportDto = (List<CheckReportDangerDto>) o;

            List<RowRenderData> checkDangerList = Lists.newArrayList();
            reportDto.forEach(foreachWithIndex((report, index) -> {
                RowRenderData rowRenderData = RowRenderData.build(String.valueOf(reportDto.size() - index),
                        report.getCompanyName(), report.getDangerName(), report.getDangerStateName(), report.getCompanyId());
                checkDangerList.add(rowRenderData);
            }));
            // companyId字段在RowRenderData中下标(0开始，companyName:1,dangerName:2,dangerStateName:3,companyId:4)
            generateTableData(table, checkDangerList, 4);
        }
    }

    public static void generateTableData(XWPFTable table, List<RowRenderData> dangerList, int bizIndex) {
        if (!ValidationUtil.isEmpty(dangerList)) {
            // 表格渲染和列表数据下标相反，需要翻转一下列表
            List<RowRenderData> reverseList = Lists.reverse(dangerList);
            table.removeRow(dangerListDataStartRow);
            int lastRow = dangerListDataStartRow;
            String sameCompanyId =
                    dangerList.get(0).getCellDatas().get(bizIndex).getRenderData().getText();
            List<Map<String, Integer>> mergeRowMapList = Lists.newArrayList();
            // 循环插入行
            int listLength = dangerList.size();
            for (int i = 0; i < listLength; i++) {
                String companyId = dangerList.get(i).getCellDatas().get(bizIndex).getRenderData().getText();
                reverseList.get(i).getCellDatas().get(0).getRenderData().setText(String.valueOf(listLength - i));
                reverseList.get(i).getCellDatas().forEach(cellRenderData -> {
                    Style style = new Style();
                    style.setFontFamily("仿宋");
                    style.setFontSize(12);
                    cellRenderData.getRenderData().setStyle(style);
                });
                XWPFTableRow insertNewTableRow = table.insertNewTableRow(dangerListDataStartRow);
                // 生成表格字段个数 (不包含companyId字段)
                int cellLength = reverseList.get(i).getCellDatas().size() - 1;
                IntStream.range(0, cellLength).forEach(j -> insertNewTableRow.createCell());

                if (!sameCompanyId.equals(companyId)) {
                    sameCompanyId = dangerList.get(i).getCellDatas().get(bizIndex).getRenderData().getText();
                    Map<String, Integer> mergeRowMap = Maps.newHashMap();
                    mergeRowMap.put("fromRow", lastRow);
                    mergeRowMap.put("toRow", i + dangerListDataStartRow - 1);
                    mergeRowMapList.add(mergeRowMap);
                    lastRow = i + dangerListDataStartRow;
                }
                // 到最后一行如果companyId都相等就合并到最后一行
                if (i == listLength - 1) {
                    Map<String, Integer> mergeRowMap = Maps.newHashMap();
                    mergeRowMap.put("fromRow", lastRow);
                    mergeRowMap.put("toRow", i + dangerListDataStartRow);
                    mergeRowMapList.add(mergeRowMap);
                }
                // 渲染检查隐患数据
                MiniTableRenderPolicy.Helper.renderRow(table, dangerListDataStartRow, reverseList.get(i));
            }
            mergeRowMapList.forEach(m -> {
                TableTools.mergeCellsVertically(table, 1, m.get("fromRow"), m.get("toRow"));
            });
        }
    }

    public static class ReviewDangerTablePolicy extends DynamicTableRenderPolicy {
        // 表格填充数据所在行数
        int dangerListDataStartRow = 2;

        @Override
        public void render(XWPFTable table, Object o) {
            if (ValidationUtil.isEmpty(o)) {
                table.removeRow(dangerListDataStartRow);
                return;
            }
            List<CheckReportDangerDto> reportDto = (List<CheckReportDangerDto>) o;

            List<RowRenderData> reviewDangerList = Lists.newArrayList();
            reportDto.forEach(foreachWithIndex((report, index) -> {
                RowRenderData rowRenderData = RowRenderData.build(String.valueOf(reportDto.size() - index),
                        report.getCompanyName(), report.getDangerName(), report.getDangerStateName(),
                        report.getRemark(), report.getCompanyId());
                reviewDangerList.add(rowRenderData);
            }));
            // companyId字段在RowRenderData中下标(0开始，companyName:1,dangerName:2,dangerStateName:3,remark:4,companyId:5)
            generateTableData(table, reviewDangerList, 5);
        }
    }

    /**
     * 获取本单位orgCode
     *
     * @param orgCode
     * @return
     */
    private String getParentOrgCode(String orgCode) {
        if (orgCode.contains("-")) {
            return orgCode.substring(0, orgCode.indexOf("-"));
        }
        return orgCode;
    }
}
