package jnpf.util.excel;


import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import jnpf.model.personnels.bo.FtbRosterImportTemplateBO;
import jnpf.personnels.listeners.BaseEasyExcelCommonListener;
import jnpf.personnels.listeners.BaseEasyExcelReadListener;
import lombok.Data;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.poifs.filesystem.FileMagic;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.ResourceUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * excel工具类
 *
 * @author fantaibao
 * @date 2024/02/02
 */
@UtilityClass
@Slf4j
public class EasyExcelUtils {

    public static final int BUFFER_SIZE = 4096;


    /**
     * 校验上传的文件是否是Excel文件及是否是xls或xlsx格式
     *
     * @param file 文件
     */
    public InputStream checkExcelFile(String file) {
        try {
            URL url = ResourceUtils.getURL(file);
            UrlResource urlResource = new UrlResource(url);
            // 根据Excel魔数判断该文件是否为Excel文件
            InputStream inputStream = urlResource.getInputStream();
            InputStream fileMagics = FileMagic.prepareToCheckMagic(inputStream);
            FileMagic fileMagic = FileMagic.valueOf(fileMagics);

            // FileMagic.OLE2表示xls格式 FileMagic.OOXML表示xlsx格式
            if (Objects.equals(fileMagic, FileMagic.OLE2) || Objects.equals(fileMagic, FileMagic.OOXML)) {
                return fileMagics;
            } else {
                log.error("file format error,please upload an Excel file in xls or xlsx format");
                throw new RuntimeException("文件格式错误，请上传xls或xlsx格式的Excel文件");
            }
        } catch (Exception e) {
            log.error("请上传xls或xlsx格式的Excel文件", e);
            throw new RuntimeException(e.getMessage());
        }

    }

    /**
     * 导出excel
     * @param response 响应
     * @param fileName 文件名
     * @param list 数据
     * @throws IOException
     */
    public <T> void exportExcel(HttpServletResponse response, String fileName, List<T> list,Class<T> tClass) throws IOException {
        String fileRealName = URLEncoder.encode(fileName+System.currentTimeMillis()/1000, "UTF-8").replaceAll("\\+", "%20");
        try(ServletOutputStream outputStream = response.getOutputStream()){
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileRealName + ".xlsx");
            EasyExcel.write(outputStream, tClass)
                    .registerWriteHandler(new EasyExcelUtils.CustomCellWriteHandler())
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .inMemory(true)
                    .sheet("sheet1").doWrite(list);
        }
    }

    /**
     * 动态表头生成（通过模板）
     */
    public void dynamicHeaderGeneration(HttpServletResponse httpServletResponse,
                                        String fileNameOriginal, String fileSource,
                                        List<FtbRosterImportTemplateBO> ftbRosterImportTemplateBOS, String employeeType) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        httpServletResponse.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode(fileNameOriginal + System.currentTimeMillis(), "UTF-8").replaceAll("\\+", "%20");
        httpServletResponse.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        if (fileSource.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
            fileSource = fileSource.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length());
        }
        try (InputStream inputStream = new ClassPathResource(fileSource).getInputStream(); OutputStream outputStream = httpServletResponse.getOutputStream()) {
            Map<String, Object> map = MapUtils.newHashMap();
            // 员工类型
            map.put("employeeType", employeeType);
            ExcelWriter excelWriter = EasyExcel.write()
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .registerWriteHandler(new CustomRosterCellWriteHandler())
                    .withTemplate(inputStream)
                    .file(outputStream)
                    .inMemory(true).build();
            WriteSheet writeSheetFill = EasyExcel.writerSheet(0).build();
            excelWriter.fill(map, writeSheetFill);
            for (int i = 0; i < ftbRosterImportTemplateBOS.size(); i++) {
                FtbRosterImportTemplateBO ftbRosterImportTemplateBO = ftbRosterImportTemplateBOS.get(i);
                // 这里放入动态头,跳过第一个sheet
                WriteSheet writeSheet = EasyExcel.writerSheet(ftbRosterImportTemplateBO.getSheetName())
                        .head(ftbRosterImportTemplateBO.getHeader()).build();
                excelWriter.write(ftbRosterImportTemplateBO.getData(), writeSheet);
            }
            excelWriter.finish();
        }

    }

    /**
     * 动态表头生成
     */
    public void dynamicHeaderGeneration(HttpServletResponse httpServletResponse,
                                        String fileNameOriginal, List<FtbRosterImportTemplateBO> ftbRosterImportTemplateBOS) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        httpServletResponse.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode(fileNameOriginal + System.currentTimeMillis(), "UTF-8").replaceAll("\\+", "%20");
        httpServletResponse.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        try (OutputStream outputStream = httpServletResponse.getOutputStream()) {
            ExcelWriter excelWriter = EasyExcel.write()
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .registerWriteHandler(new CustomCellWriteHandler())
                    .file(outputStream)
                    .inMemory(true).build();
            for (int i = 0; i < ftbRosterImportTemplateBOS.size(); i++) {
                FtbRosterImportTemplateBO ftbRosterImportTemplateBO = ftbRosterImportTemplateBOS.get(i);
                // 这里放入动态头,跳过第一个sheet
                WriteSheet writeSheet = EasyExcel.writerSheet(ftbRosterImportTemplateBO.getSheetName())
                        .head(ftbRosterImportTemplateBO.getHeader()).build();
                excelWriter.write(ftbRosterImportTemplateBO.getData(), writeSheet);
            }
            excelWriter.finish();
        }
    }

    /**
     * 动态表头生成
     */
    public void dynamicHeaderGenerationRoster(HttpServletResponse httpServletResponse,
                                        String fileNameOriginal, List<FtbRosterImportTemplateBO> ftbRosterImportTemplateBOS) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        httpServletResponse.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode(fileNameOriginal + System.currentTimeMillis(), "UTF-8").replaceAll("\\+", "%20");
        httpServletResponse.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        try (OutputStream outputStream = httpServletResponse.getOutputStream()) {
            ExcelWriter excelWriter = EasyExcel.write()
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .registerWriteHandler(new CustomRosterCellWriteHandler())
                    .file(outputStream)
                    .inMemory(true).build();
            for (int i = 0; i < ftbRosterImportTemplateBOS.size(); i++) {
                FtbRosterImportTemplateBO ftbRosterImportTemplateBO = ftbRosterImportTemplateBOS.get(i);
                // 这里放入动态头,跳过第一个sheet
                WriteSheet writeSheet = EasyExcel.writerSheet(ftbRosterImportTemplateBO.getSheetName())
                        .head(ftbRosterImportTemplateBO.getHeader()).build();
                excelWriter.write(ftbRosterImportTemplateBO.getData(), writeSheet);
            }
            excelWriter.finish();
        }
    }

    /**
     * 动态表头生成(匹配对应sheet 对应的数据)
     */
    public void dynamicHeaderGeneration(HttpServletResponse httpServletResponse,
                                        String fileNameOriginal, String fileSource,
                                        List<FtbRosterImportTemplateBO> ftbRosterImportTemplateBOS,
                                        List<ExcelSelectedResolve> pullList) throws IOException {
        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        httpServletResponse.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode(fileNameOriginal + System.currentTimeMillis(), "UTF-8").replaceAll("\\+", "%20");
        httpServletResponse.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        if (fileSource.startsWith(ResourceUtils.CLASSPATH_URL_PREFIX)) {
            fileSource = fileSource.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length());
        }
        // 匹配对应的数据进行处理 数据下标构建
        for (ExcelSelectedResolve excelSelectedResolve : pullList){
            List<String> list = new ArrayList<>();
            list.add(excelSelectedResolve.getHeaderName());
            int j = ftbRosterImportTemplateBOS.stream()
                    .filter(vo -> excelSelectedResolve.getSheetName().equals(vo.getSheetName()))
                    .findFirst()
                    .orElse(new FtbRosterImportTemplateBO())
                    .getHeader()
                    .indexOf(list);
            excelSelectedResolve.setRowNum(j);
        }
        try (InputStream inputStream = new ClassPathResource(fileSource).getInputStream(); OutputStream outputStream = httpServletResponse.getOutputStream()) {
            ExcelWriter excelWriter = EasyExcel.write()
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                    .registerWriteHandler(new CustomCellWriteHandler())
                    .withTemplate(inputStream)
                    .file(outputStream)
                    .inMemory(true).build();
            for (int i = 0; i < ftbRosterImportTemplateBOS.size(); i++) {
                FtbRosterImportTemplateBO ftbRosterImportTemplateBO = ftbRosterImportTemplateBOS.get(i);
                // 这里放入动态头,跳过第一个sheet
                WriteSheet writeSheet = EasyExcel.writerSheet(ftbRosterImportTemplateBO.getSheetName())
                        .registerWriteHandler(new SelectedSheetWriteHandler(pullList))
                        .head(ftbRosterImportTemplateBO.getHeader()).build();
                excelWriter.write(ftbRosterImportTemplateBO.getData(), writeSheet);
            }
            excelWriter.finish();
        }

    }


    /**
     * 通用导入
     */
    public <T, R> void universalImport(Class<T> tClass,
                                       InputStream inputStream,
                                       BaseEasyExcelCommonListener<T, R> baseEasyExcelCommonListener,
                                       Integer sheetNo,
                                       Integer... integers) throws IOException {
        int integer = 1;
        if (integers.length > 0) integer = integers[0];
        baseEasyExcelCommonListener.before();
        EasyExcel.read(inputStream, tClass, new BaseEasyExcelReadListener<>(baseEasyExcelCommonListener))
                .headRowNumber(integer)
                .sheet(sheetNo).doRead();
        baseEasyExcelCommonListener.after();
    }

    @Slf4j
    public class CustomRosterCellWriteHandler implements CellWriteHandler {

        @Override
        public void afterCellDispose(CellWriteHandlerContext context) {
            Cell cell = context.getCell();
            // 表头样式判断
            if (cell.getRowIndex() == 0) {
                Workbook workbook = cell.getSheet().getWorkbook();
                Font font = workbook.createFont();
                font.setBold(true);
                font.setFontHeightInPoints((short) 11);
                XSSFRichTextString richTextString = new XSSFRichTextString(cell.getStringCellValue());
                if (cell.getStringCellValue().contains("*")) {
                    font.setColor(IndexedColors.RED.getIndex());
                    richTextString.applyFont(0, 1, font);
                    font.setColor(IndexedColors.BLACK.getIndex());
                    richTextString.applyFont(1, cell.getStringCellValue().length(), font);
                } else {
                    font.setColor(IndexedColors.BLACK.getIndex());
                    richTextString.applyFont(font);
                }
                cell.setCellValue(richTextString);
            }
        }


    }

    @Slf4j
    public class CustomCellWriteHandler implements CellWriteHandler {

        @Override
        public void afterCellDispose(CellWriteHandlerContext context) {
            Cell cell = context.getCell();
            // 表头样式判断
            if (cell.getRowIndex() == 0) {
                Workbook workbook = cell.getSheet().getWorkbook();
                Font font = workbook.createFont();
                font.setBold(true);
                font.setFontHeightInPoints((short) 11);
                
                String cellValue = cell.getStringCellValue();
                if (cellValue != null && !cellValue.isEmpty()) {
                    if (cellValue.contains("*")) {
                        font.setColor(IndexedColors.RED.getIndex());
                        // 使用 POI 的原生方式设置样式，避免转换为 RichTextString
                        CellStyle cellStyle = cell.getCellStyle();
                        cellStyle.setFont(font);
                    } else {
                        font.setColor(IndexedColors.BLACK.getIndex());
                        CellStyle cellStyle = cell.getCellStyle();
                        cellStyle.setFont(font);
                    }
                }
            }
        }


    }

    @Slf4j
    @Data
    public class SelectedSheetWriteHandler implements SheetWriteHandler{

        private final List<ExcelSelectedResolve> resolveList;
        @Override
        public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
            // 这里可以对cell进行任何操作
            Sheet sheet = writeSheetHolder.getSheet();
            String sheetName = sheet.getSheetName();
            DataValidationHelper helper = sheet.getDataValidationHelper();
            for (ExcelSelectedResolve resolve : resolveList) {
                if (!sheetName.equals(resolve.getSheetName()) || resolve.getRowNum() == -1){
                    continue;
                }
                // 设置下拉列表的行： 首行，末行，首列，末列
                CellRangeAddressList rangeList = new CellRangeAddressList(resolve.getFirstRow(),
                        resolve.getLastRow(),
                        resolve.getRowNum(),
                        resolve.getRowNum());
                // 设置下拉列表的值
                DataValidationConstraint constraint = helper.createExplicitListConstraint(resolve.getSource());
                // 设置约束
                DataValidation validation = helper.createValidation(constraint, rangeList);
                // 阻止输入非下拉选项的值
                validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
                validation.setShowErrorBox(true);
                validation.setSuppressDropDownArrow(true);
                validation.createErrorBox("提示", "请输入下拉选项中的内容");
                sheet.addValidationData(validation);
            }
        }
    }
}


