package com.simuwang.daq.components.report.parser.pdf;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import com.simuwang.base.common.conts.Constants;
import com.simuwang.base.common.enums.ReportType;
import com.simuwang.base.common.exception.ReportParseException;
import com.simuwang.base.mapper.EmailFieldMappingMapper;
import com.simuwang.base.pojo.dto.report.*;
import com.simuwang.daq.components.CustomPDFTextStripper;
import com.simuwang.daq.components.report.parser.AbstractReportParser;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.io.RandomAccessReadBufferedFile;
import org.apache.pdfbox.pdmodel.PDDocument;
import technology.tabula.CustomObjectExtractor;
import technology.tabula.Page;
import technology.tabula.PageIterator;
import technology.tabula.Table;
import technology.tabula.extractors.SpreadsheetExtractionAlgorithm;

import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author wangzaijun
 * @date 2024/9/29 16:45
 * @description pdf格式的报告解析抽象类
 */
public abstract class AbstractPDReportParser<T extends ReportData> extends AbstractReportParser<T> {
    /**
     * 基金信息表格
     */
    protected Table fundInfoTable;
    /**
     * 去除了水印的所有文本内容
     */
    protected List<String> textList;

    public AbstractPDReportParser(EmailFieldMappingMapper fieldMappingMapper) {
        super(fieldMappingMapper);
    }

    @Override
    public T parse(ReportParserParams params) throws IOException, ReportParseException {
        // 先初始化为null
        this.fundInfoTable = null;
        this.textList = null;
        // 初始化
        this.init();
        // 解析报告和表格
        try (PDDocument document = Loader.loadPDF(new RandomAccessReadBufferedFile(params.getFilepath()))) {
            // 识别所有文字（去水印后的）
            CustomPDFTextStripper stripper = new CustomPDFTextStripper();
            stripper.setSortByPosition(true);
            String text = stripper.getText(document).replace(Constants.WATERMARK_REPLACE, StrUtil.EMPTY);
            this.textList = StrUtil.split(text, System.lineSeparator());
            this.textList.removeIf(StrUtil::isBlank);
            if (this.textList.isEmpty()) {
                throw new ReportParseException(ReportParseStatus.REPORT_IS_SCAN);
            }
            // 报告名称和类型一般在第一第二行
            if (this.matchReportType(this.textList.get(0)) == null && this.matchReportType(this.textList.get(1)) == null) {
                throw new ReportParseException(ReportParseStatus.NOT_A_REPORT);
            }
            // 解析所有表格（单元格字符去水印）
            List<Table> tables = ListUtil.list(true);
            SpreadsheetExtractionAlgorithm extractionAlgorithm = new SpreadsheetExtractionAlgorithm();
            // 自定义表格提取工具，去除单元格中的水印文字
            PageIterator pageIterator = new CustomObjectExtractor(document).extract();
            while (pageIterator.hasNext()) {
                Page page = pageIterator.next();
                tables.addAll(extractionAlgorithm.extract(page));
            }
            if (tables.isEmpty()) {
                throw new ReportParseException(ReportParseStatus.REPORT_IS_SCAN);
            }
            this.initTableInfo(tables);
        }
        try {
            // 报告基本信息
            ReportBaseInfoDTO reportInfo = this.buildReportInfo(params);
            // 解析报告中主体基金的基本信息
            ReportFundInfoDTO reportFundInfo = this.buildFundInfo(params);
            // 解析其他表格信息并且设置结果字段
            T reportData = this.parseExtInfoAndSetData(reportInfo, reportFundInfo);
            // 数据清洗后返回
            this.cleaningReportData(reportData);
            return reportData;
        } catch (ReportParseException e) {
            throw e;
        } catch (Exception e) {
            this.logger.warn("报告解析错误：{}", ExceptionUtil.stacktraceToString(e));
            throw new ReportParseException(ReportParseStatus.NOT_A_FIXED_FORMAT);
        }
    }

    /**
     * 初始化解析所有表格数据
     *
     * @param tables 按固定的表格模式划分到不同的对象中
     */
    protected abstract void initTableInfo(List<Table> tables);

    /**
     * 绑定基金基本信息（年报的基金基本信息解析逻辑要覆盖重写）
     *
     * @param params /
     * @return /
     */
    protected ReportFundInfoDTO buildFundInfo(ReportParserParams params) {
        Table fundInfoTable = this.fundInfoTable;
        if (fundInfoTable == null) {
            throw new ReportParseException(ReportParseStatus.PARSE_FUND_INFO_FAIL);
        }
        // 基金基本信息映射
        return this.buildDto(params.getFileId(), fundInfoTable, ReportFundInfoDTO.class, this::parseFundInfo);
    }

    /**
     * 解析基金基本信息表格
     *
     * @param fundInfoTable 表格
     * @return /
     */
    protected abstract Map<String, Object> parseFundInfo(Table fundInfoTable);

    /**
     * 解析报告的其他信息并设置到对象中
     *
     * @param reportInfo 报告基本信息
     * @param fundInfo   报告中基金基本信息
     * @return /
     */
    protected abstract T parseExtInfoAndSetData(ReportBaseInfoDTO reportInfo, ReportFundInfoDTO fundInfo);

    @Override
    protected void cleaningReportData(T reportData) {
        // cleaning.
    }

    /**
     * 构建报告基本信息
     *
     * @param params /
     * @return /
     */
    private ReportBaseInfoDTO buildReportInfo(ReportParserParams params) {
        Integer fileId = params.getFileId();
        String reportName = params.getFilename();
        ReportBaseInfoDTO reportInfo = new ReportBaseInfoDTO(fileId);
        reportInfo.setReportName(reportName);
        reportInfo.setReportType(this.matchReportType(reportName));
        reportInfo.setReportDate(this.matchReportDate(reportName));
        return reportInfo;
    }

    /**
     * 构建只有两列表格的dto数据对象，如果有分级基金时
     *
     * @param <DTO>    泛型对象
     * @param fileId   文件id
     * @param tables   表格
     * @param clazz    泛型对象
     * @param function 表格转换的函数
     * @return /
     */
    protected <DTO extends BaseReportLevelDTO<?>> List<DTO> buildLevelDto(Integer fileId, List<Table> tables, Class<DTO> clazz,
                                                                          Function<Table, Map<String, Object>> function) {
        // 映射转换
        List<DTO> dtos = tables.stream().filter(Objects::nonNull)
                .map(e -> this.buildDto(fileId, e, clazz, function)).collect(Collectors.toList());
        // 分级基金匹配
        List<String> levels = this.matchTieredFund(String.join(",", this.textList));
        levels.add(0, "母基金");
        for (int i = 0; i < dtos.size(); i++) {
            if (levels.size() <= i) {
                continue;
            }
            dtos.get(i).setLevel(levels.get(i));
        }
        return dtos;
    }

    /**
     * 构建只有两列表格的dto数据对象
     *
     * @param <DTO>    泛型对象
     * @param fileId   文件id
     * @param table    表格
     * @param clazz    泛型对象
     * @param function 表格转换的函数
     * @return /
     */
    private <DTO extends BaseReportDTO<?>> DTO buildDto(Integer fileId, Table table, Class<DTO> clazz,
                                                        Function<Table, Map<String, Object>> function) {
        try {
            Map<String, Object> extInfoMap = function == null ? MapUtil.empty() : function.apply(table);
            DTO dto = clazz.getDeclaredConstructor().newInstance();
            dto.setFileId(fileId);
            this.buildInfo(extInfoMap, dto);
            return dto;
        } catch (Exception ignored) {
        }
        return null;
    }

    /**
     * 匹配分级基金名称
     *
     * @param text 文本内容
     * @return /
     */
    protected List<String> matchTieredFund(String text) {
        List<String> matches = ListUtil.list(false);
        if (StrUtil.isBlank(text)) {
            return matches;
        }
        // 使用正则表达式查找匹配项
        Pattern pattern = Pattern.compile("[A-F]级|基金[A-F]");
        Matcher matcher = pattern.matcher(text);
        // 收集所有匹配项
        while (matcher.find()) {
            matches.add(matcher.group());
        }
        // 提取字母并按字母顺序排序
        return matches.stream()
                .map(s -> s.replaceAll("[^A-F]", ""))
                .distinct()
                .sorted()
                .map(letter -> letter + "级")
                .collect(Collectors.toList());
    }

    /**
     * 匹配报告日期
     *
     * @param string 文本内容
     * @return 报告日期
     */
    private String matchReportDate(String string) {
        if (string == null) {
            return null;
        }
        // 编译正则表达式模式
        Pattern pat1 = Pattern.compile("(2\\d{3}).*([一二三四1234])季度");  // 2023年XXX3季度
        Pattern pat2 = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");  // 2023-12-31
        Pattern pat3 = Pattern.compile("(2\\d{3})年年度");  // 2023年年度
        Pattern pat4 = Pattern.compile("(\\d{4})年(\\d{1,2})月");  // 2023年12月
        Pattern pat5 = Pattern.compile("\\d{4}\\d{2}\\d{2}");  // 20231231
        Pattern pat6 = Pattern.compile("(2\\d{3})年度");  // 2023年度
        // 创建Matcher对象
        Matcher matcher1 = pat1.matcher(string);
        Matcher matcher2 = pat2.matcher(string);
        Matcher matcher3 = pat3.matcher(string);
        Matcher matcher4 = pat4.matcher(string);
        Matcher matcher5 = pat5.matcher(string);
        Matcher matcher6 = pat6.matcher(string);
        // 尝试匹配
        if (matcher1.find()) {
            String year = matcher1.group(1);
            String quarter = matcher1.group(2);
            return switch (quarter) {
                case "一", "1" -> year + "-03-31";
                case "二", "2" -> year + "-06-30";
                case "三", "3" -> year + "-09-30";
                case "四", "4" -> year + "-12-31";
                default -> null;
            };
        } else if (matcher2.find()) {
            return matcher2.group();
        } else if (matcher5.find()) {
            return matcher5.group();
        } else if (matcher3.find()) {
            return matcher3.group(1) + "-12-31";
        } else if (matcher6.find()) {
            return matcher6.group(1) + "-12-31";
        } else if (matcher4.find()) {
            String year = matcher4.group(1);
            String month = matcher4.group(2);
            int lastDayOfMonth = getLastDayOfMonth(Integer.parseInt(year), Integer.parseInt(month));
            return year + "-" + padZero(month) + "-" + padZero(lastDayOfMonth + "");
        } else {
            return null;
        }
    }

    /**
     * 匹配报告类型，如“季度”、“年度”
     *
     * @param string 输入字符串
     * @return 匹配到的报告类型子字符串，如果没有匹配到则返回null
     */
    private String matchReportType(String string) {
        if (string == null) {
            return null;
        }
        // 所有报告的正则识别方式
        String patterns = ReportType.getAllPatterns();
        // 编译正则表达式模式
        Pattern pattern = Pattern.compile(patterns);
        // 创建Matcher对象
        Matcher matcher = pattern.matcher(string);
        // 尝试匹配
        if (matcher.find()) {
            return matcher.group();
        } else {
            return null;
        }
    }

    private int getLastDayOfMonth(int year, int month) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR, year);
        calendar.set(Calendar.MONTH, month - 1); // Calendar.MONTH 是从0开始的
        return calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
    }

    private String padZero(String number) {
        return String.format("%02d", Integer.parseInt(number));
    }
}
