/**
 * Copyright 2008-2009. Chongqing Communications Industry Services Co.,Ltd Information Technology Branch. All rights reserved. <a>http://www.crunii.com</a>
 */
package com.crunii.micro.common.excel;

import cn.hutool.core.lang.func.Func;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.alibaba.nacos.common.utils.StringUtils;
import com.crunii.micro.common.dto.NoModelWriteData;
import com.crunii.micro.common.exception.BusinessException;
import com.crunii.micro.common.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.http.HttpStatus;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;

/**
 * @author 张波 create 2020年10月9日上午10:20:25
 */
@Slf4j
public class ExcelUtil {

	public static boolean isExcel2003(String filePath)
    {
        return filePath.matches("^.+\\.(?i)(xls)$");
    }

    public static boolean isExcel2007(String filePath)
    {
        return filePath.matches("^.+\\.(?i)(xlsx)$");
    }

	/**
	 * 读取单个sheet的excel文件
	 *
	 * @param excel 文件
	 * @param t 实体类型
	 * @param headRowNumber 头行数
	 * @return
	 * @throws Exception
	 */
	public static <T> List<T> readSingleExcel(MultipartFile excel, T t, int headRowNumber) throws Exception {
		return EasyExcel.read(excel.getInputStream(), t.getClass(), new ExcelListenerUtil()).sheet().headRowNumber(headRowNumber).doReadSync();
	}

	/**
	 * 导出文件 导出模板时，tList传一个空list即可
	 * 专门用于OD解析配置导出普通模板
	 * @param tList 数据集
	 * @param tClass 数据类型
	 * @param <T>
	 * @throws IOException
	 */
	public static <T> void writeSingleExcel(String fileName, String sheetName, List<T> tList, Class<T> tClass) throws IOException {
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
		try (ServletOutputStream outputStream = response.getOutputStream()) {

			setResponse(fileName, response);
			EasyExcel.write(outputStream, tClass).autoCloseStream(Boolean.FALSE).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).registerWriteHandler(new ExcelSheetWriteHandler()).sheet(sheetName).doWrite(tList);
		} catch (Exception e) {
			errorWrite(response, e);
		}
	}

	

	/**
	 * 导出文件 导出模板时，tList传一个空list即可
	 * 通用
	 * @param tList 数据集
	 * @param tClass 数据类型
	 * @param <T>
	 * @throws IOException
	 */
	public static <T> void writeSingleCommonExcel(String fileName, String sheetName, List<T> tList, Class<T> tClass) throws IOException {
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
		try (ServletOutputStream outputStream = response.getOutputStream()) {
			setResponse(fileName, response);
			EasyExcel.write(outputStream, tClass).autoCloseStream(Boolean.FALSE).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet(sheetName).doWrite(tList);
		} catch (Exception e) {
			errorWrite(response, e);
		}
	}

	/**
	 * @description: 导出list<map<String,Object>>类型的数据
	 * @param
	 * @return:
	 * @author liuchuan
	 * @date: 2022/11/17 21:43
	 */
	public static void noModleWrite(NoModelWriteData data) {
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
		try {//            response.setContentType("application/vnd.ms-excel");
			response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
			response.setCharacterEncoding("utf-8");
			// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
			String fileName = URLEncoder.encode(data.getFileName(), "UTF-8");
			response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
			// 这里需要设置不关闭流
			EasyExcel.write(response.getOutputStream()).head(head(data.getHeadMap())).sheet(data.getFileName()).
					doWrite(dataList(data.getDataList(), data.getDataStrMap()));
		} catch (Exception e) {
			e.printStackTrace();
			throw new RuntimeException("导出Excel异常");

		}
	}

	//设置导出的数据内容
	private static List<List<Object>> dataList(List<Map<String, Object>> dataList, String[] dataStrMap) {
		List<List<Object>> list = new ArrayList<List<Object>>();
		for (Map<String, Object> map : dataList) {
			List<Object> data = new ArrayList<Object>();
			for (int i = 0; i < dataStrMap.length; i++) {
				data.add(map.get(dataStrMap[i]));
			}
			list.add(data);
		}
		return list;
	}

	//设置表头
	private static List<List<String>> head(String[] headMap) {
		List<List<String>> list = new ArrayList<List<String>>();

		for (String head : headMap) {
			List<String> headList = new ArrayList<String>();
			headList.add(head);
			list.add(headList);
		}
		return list;
	}

	/**
	 * 忽略部分导出字段方法
	 * @param fileName 文件名称
	 * @param sheetName sheet名称
	 * @param dataList 需要导出的数据
	 * @param clazz 类
	 * @param ignoreParam 忽略的字段
	 * @param <T>
	 * @throws IOException
	 */
	public static <T> void export(String fileName, String sheetName, List<T> dataList, Class<T> clazz, Set<String> ignoreParam) throws IOException {
		HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
		try (ServletOutputStream outputStream = response.getOutputStream()) {
			setResponse(fileName, response);
			if (ignoreParam.size() > 0) {
				EasyExcel.write(outputStream, clazz).excludeColumnFiledNames(ignoreParam).sheet(sheetName).doWrite(dataList);
			} else {
				EasyExcel.write(outputStream, clazz).sheet(sheetName).doWrite(dataList);
			}
		}catch (Exception e) {
			errorWrite(response, e);
		}
	}

	/**
	 * 导出错误
	 *
	 * @param response
	 * @param e
	 * @throws IOException
	 */
	private static void errorWrite(HttpServletResponse response, Exception e) throws IOException {
		// 重置response
		response.reset();
		log.error(e.getMessage(), e);
		response.setContentType("application/json");
		response.setCharacterEncoding("utf-8");
		response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
		// response.getWriter().println(JsonUtil.getJsonString(BaseResponse.failure(HttpStatus.INTERNAL_SERVER_ERROR.value(), "导出失败")));
	}

	/**
	 * 设置导出信息
	 *
	 * @param fileName
	 * @param response
	 * @throws UnsupportedEncodingException
	 */
	private static void setResponse(String fileName, HttpServletResponse response) throws UnsupportedEncodingException {
		// 重置response
		response.reset();
		// response.setContentType("application/octet-stream;charset=utf-8");
		response.setContentType("application/vnd.ms-excel");
		response.setCharacterEncoding("utf-8");
		// 这里URLEncoder.encode可以防止中文乱码
		fileName = URLEncoder.encode(fileName + DateUtil.format(new Date(), "yyyy-MM-dd_HH_mm_ss") + ExcelTypeEnum.XLSX.getValue(), "UTF-8");
		response.setHeader("Content-disposition", "attachment;filename=" + fileName);
	}

	/**
     * 校验模板是否正确
     * tClass是传入对应的excel表头实体
     * rowNo 头行号，如果复杂头，取最下层头行号
     * @param file
     */
    public static <T> void checkModel(MultipartFile file,Class<T> tClass,Integer rowNo) {
        // 校验模板是否正确
        List<String> columnNames = getColumnNames(file,0,rowNo);
        Field[] declaredFields = tClass.getDeclaredFields();
        // 判断列数是否等于对象变量数量
        if(columnNames.size() != declaredFields.length){
            throw new BusinessException("上传模板错误!");
        }
        for (int i = 0,fLength = declaredFields.length ; i <fLength; i++) {
            Field declaredField = declaredFields[i];
            declaredField.setAccessible(true);
            ExcelProperty annotation = declaredField.getAnnotation(ExcelProperty.class);
            String[] values = annotation.value();
            String columnValue = values[values.length-1];
            if(!columnValue.equals(columnNames.get(i))){
            	throw new BusinessException("上传模板错误!");
            }
        }
    }

    /**
     * 获取文件头列名称
     *
     * @param file 文件
     * @param sheetNo sheet编号
     * @param rowNo 头行号，如果复杂头，取最下层头行号
     * @return
     */
    public static List<String> getColumnNames(MultipartFile file,Integer sheetNo,Integer rowNo) {
        List<String> columnNames = new ArrayList<>(28);
        Workbook workBook = getWorkBook(file);
        Iterator<Cell> iterator = workBook.getSheetAt(sheetNo).getRow(rowNo).iterator();
        while (true){
            if(iterator.hasNext()){
            	Cell cell = iterator.next();
            	//设置单元格类型
            	cell.setCellType(CellType.STRING);
            	//获取单元格数据
                String columnName = cell.getStringCellValue();
                columnNames.add(columnName);
            } else {
                break;
            }
        }
        return columnNames;
    }

    /**
     * 得到Workbook对象
     *
     * @param file
     * @return
     */
    public static Workbook getWorkBook(MultipartFile file) {
        Workbook workbook = null;
        try (InputStream inputStream = file.getInputStream()) {
            String filename = file.getOriginalFilename();
            if(isExcel2003(filename)){
                workbook = new HSSFWorkbook(inputStream);
            }else if(isExcel2007(filename)){
                workbook = new XSSFWorkbook(inputStream);
            }else {
                throw new BusinessException("文件类型错误，请上传Excel文件!");
            }
        } catch (IOException e) {
            log.error(e.getMessage(),e);
        }
        return workbook;
    }

}