package com.youchain.utils;

import cn.idev.excel.ExcelReader;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.read.metadata.ReadSheet;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.metadata.style.WriteCellStyle;
import cn.idev.excel.write.metadata.style.WriteFont;
import cn.idev.excel.write.style.HorizontalCellStyleStrategy;

import com.youchain.exception.BadRequestException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.MediaTypeFactory;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.ToLongFunction;
import java.util.stream.Collectors;

import static cn.hutool.core.util.CharsetUtil.UTF_8;


/**
 * excel 工具类
 */
@Slf4j
public class FastExcelUtil {

    /**
     * 设置下载文件消息头
     *
     * @param response 响应
     * @param fileName 文件名
     * @param fileSize 文件大小
     */
    public static void setDownloadFileHeader(HttpServletResponse response, String fileName, Long fileSize) {
        response.setCharacterEncoding(UTF_8);
        if (fileSize != null) {
            response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(fileSize));
        }

        if (StringUtils.isNotEmpty(fileName)) {
            response.setHeader(HttpHeaders.CONTENT_TYPE, MediaTypeFactory.getMediaType(fileName).orElse(MediaType.APPLICATION_OCTET_STREAM) + ";charset=utf-8");
            try {
                response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + URLEncoder.encode(fileName, UTF_8).replaceAll("\\+", "%20"));
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
            response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
        }
    }

    /**
     * 获取样式
     *
     * @return horizontalCellStyleStrategy
     */
    public static HorizontalCellStyleStrategy getHorizontalCellStyleStrategy() {

        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        headWriteCellStyle.setFillForegroundColor(IndexedColors.SKY_BLUE.getIndex());

        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontName("宋体");
        headWriteFont.setColor(IndexedColors.WHITE.getIndex());
        headWriteCellStyle.setWriteFont(headWriteFont);

        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        contentWriteCellStyle.setFillForegroundColor(IndexedColors.BLACK.getIndex());

        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
    }

    /**
     * 通用单sheet导出
     */
    public static void exportExcel(HttpServletResponse response, String fileName, String sheetName, Class head, Collection<?> data) throws IOException {
        // 设置下载消息头
        setDownloadFileHeader(response, fileName, null);
        //样式
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = getHorizontalCellStyleStrategy();
        // 下载
        FastExcel.write(response.getOutputStream(), head)
                .autoCloseStream(Boolean.FALSE)
                .sheet(sheetName)
                .registerWriteHandler(horizontalCellStyleStrategy)
                .doWrite(data);
    }

    /**
     * 大数据导出
     *
     * @param response          响应
     * @param fileName          文件名
     * @param sheetName         sheet名称
     * @param head              头
     * @param pageQueryFunction 查询方法
     * @param convertFunction   转换方法
     * @param idExtractor       id提取方法
     * @param pageSize          每页大小
     * @param <T>               泛型
     * @param <E>               泛型
     */
    public static <T, E> void batchExportExcel(HttpServletResponse response, String fileName, String sheetName, Class head, BiFunction<Long, Integer, List<T>> pageQueryFunction, Function<T, E> convertFunction, ToLongFunction<T> idExtractor, int pageSize) {
        long startTime = System.currentTimeMillis();
        // 设置下载消息头
        setDownloadFileHeader(response, fileName, null);
        HorizontalCellStyleStrategy horizontalCellStyleStrategy = getHorizontalCellStyleStrategy();
        try (ExcelWriter excelWriter = FastExcel.write(response.getOutputStream(), head).registerWriteHandler(horizontalCellStyleStrategy).build()) {
            WriteSheet writeSheet = FastExcel.writerSheet(sheetName).build();
            long lastId = 0;//最后一个id
            while (true) {
                List<T> batch = pageQueryFunction.apply(lastId, pageSize);
                if (batch.isEmpty()) {
                    break;
                }

                List<E> excelData = batch.stream()
                        .map(convertFunction)
                        .collect(Collectors.toList());
                excelWriter.write(excelData, writeSheet);

                lastId = batch.stream()
                        .mapToLong(idExtractor)
                        .max()
                        .orElse(lastId);
            }
            excelWriter.finish();
            log.info("导出总耗时: {}ms", System.currentTimeMillis() - startTime);
        } catch (Exception e) {
            throw new BadRequestException("导出失败");
        }
    }


    /**
     * 从 Excel 文件中读取指定类型的数据
     *
     * @param file  上传的 Excel 文件
     * @param clazz 数据映射的类（如 Template1.class）
     * @param <T>   数据类型
     * @return 解析后的数据列表
     */
    public static <T> List<T> readExcelData(MultipartFile file, Class<T> clazz, int sheetNo, int headRowNumber) {
        List<T> dataList;
        try {
            dataList = FastExcel.read(file.getInputStream()).head(clazz)
                    .sheet(sheetNo)
                    .headRowNumber(headRowNumber)
                    .doReadSync();
        } catch (IOException e) {
            throw new BadRequestException("数据格式存在问题，无法读取");
        }
        if (CollectionUtils.isEmpty(dataList)) {
            throw new BadRequestException("数据为空");
        }
        return dataList;
    }


    public static List<Map<Integer, Object>> readExcelData2(MultipartFile file, int headRowNumber) {
        try {
            return FastExcel.read(file.getInputStream())
                    .sheet()
                    .headRowNumber(headRowNumber)
                    .doReadSync();
        } catch (IOException e) {
            throw new BadRequestException("数据格式存在问题，无法读取");
        }
    }


    /**
     * 读取Excel文件中指定位置的单元格值
     *
     * @param file   Excel文件
     * @param reader 指定行和列
     */
    public static Map<String, String> readExcelCellValues(MultipartFile file, FastExcelCellListener reader, int sheetNo) {
        try {
            FastExcel.read(file.getInputStream(), reader)
                    .sheet(sheetNo)
                    .doReadSync();
        } catch (IOException e) {
            throw new BadRequestException("数据格式存在问题，无法读取");
        }

        return reader.getAllCellValues();
    }

    /**
     * 读取 Excel 文件头部的所有内容（第一行）
     *
     * @param inputStream Excel 文件输入流
     * @return 头部内容列表
     */
    public static List<String> readHeadContent(InputStream inputStream, int sheetNo, int headRowNumber) {
        List<Object> data = FastExcel.read(inputStream)
                .sheet(sheetNo)
                .headRowNumber(headRowNumber)
                .doReadSync();
        // 检查是否有数据
        if (data.isEmpty()) {
            throw new BadRequestException("导入模板不正确,请确认模板信息");
        }
        Object firstRow = data.get(0);
        // 将头部内容转换为字符串列表
        if (firstRow instanceof Map) {
            Map<?, ?> rowMap = (Map<?, ?>) firstRow;
            return rowMap.values().stream()
                    .map(value -> value == null ? "" : value.toString())
                    .collect(Collectors.toList());
        } else {
            throw new BadRequestException("读取Excel头部内容格式异常");
        }
    }

    /**
     * 验证 Excel 文件头部是否包含指定的列名
     *
     * @return 验证结果，true 表示包含所有必需列
     */
    public static List<String> readHeadContent(MultipartFile file, int sheetNo, int headRowNumber) {
        try {
            //判断头部内容是否包含所有必填列
            log.info(file.getInputStream()+"");
            return readHeadContent(file.getInputStream(), sheetNo, headRowNumber);
        } catch (IOException e) {
            throw new BadRequestException("数据格式存在问题，无法读取");
        }
    }

    public static List<ReadSheet> readSheets(MultipartFile file) {
        try (ExcelReader excelReader = FastExcel.read(file.getInputStream()).build()) {
            // 获取所有Sheet
            List<ReadSheet> allSheets = excelReader.excelExecutor().sheetList();

            // 使用 POI 检测隐的sheet
            Set<String> hiddenSheetNames = getHiddenSheetNames(file);

            //过滤隐藏的sheet
            return allSheets.stream()
                    .filter(sheet -> !hiddenSheetNames.contains(sheet.getSheetName()))
                    .collect(Collectors.toList());
        } catch (IOException e) {
            log.error("读取Excel Sheet信息失败", e);
            throw new BadRequestException("读取Excel Sheet信息失败");
        }

    }

    /**
     * 获取隐藏的Sheet名称
     *
     * @param file 文件
     */
    private static Set<String> getHiddenSheetNames(MultipartFile file) {
        Set<String> hiddenSheets = new HashSet<>();

        try (InputStream inputStream = file.getInputStream();
             Workbook workbook = WorkbookFactory.create(inputStream)) {

            int numberOfSheets = workbook.getNumberOfSheets();
            for (int i = 0; i < numberOfSheets; i++) {
                if (workbook.isSheetHidden(i) || workbook.isSheetVeryHidden(i)) {
                    String sheetName = workbook.getSheetName(i);
                    hiddenSheets.add(sheetName);
                }
            }
        } catch (Exception e) {
            throw new BadRequestException("获取隐藏的Sheet名称失败");
        }

        return hiddenSheets;
    }


}
