package com.qmth.teachcloud.common.util;

import com.qmth.boot.tools.excel.ExcelReader;
import com.qmth.boot.tools.excel.annotation.ExcelColumn;
import com.qmth.boot.tools.excel.enums.ExcelType;
import com.qmth.teachcloud.common.annotation.ExcelError;
import com.qmth.teachcloud.common.bean.result.ExcelResult;
import com.qmth.teachcloud.common.contant.SystemConstant;
import com.qmth.teachcloud.common.enums.ExceptionResultEnum;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPageTree;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Description: 转换工具类
 * @Author: CaoZixuan
 * @Date: 2021-04-14
 */

@Component
public class ConvertUtil {

    private final static Logger log = LoggerFactory.getLogger(ConvertUtil.class);

    /**
     * 转换，解析时间
     *
     * @param date 日期(考试日期，必须是同一天     example: 2021-04-13)
     * @param time 时间(考试时间，必须使用‘-’隔开  example: 18:00-20:00)
     * @return 开始时间和结束时间键值对(" startTime " : 1618308000000, " endTime " : 1618315200000)
     */
    public static Map<String, Object> analyzeStartAndEndTime(String date, String time) {
        time = time.replaceAll(" ", ""); // 去掉所有空格
        String[] arr = time.split(SystemConstant.HYPHEN);
        if (arr.length != 2) {
            // 不能使用'-'拆分成两个时间的报错
            throw ExceptionResultEnum.ERROR.exception("提供的时间格式异常，考试时间[" + time + "]应该使用如下的格式（考试时间:18:00-20:00）");
        }
        String startTimeStr = date + " " + arr[0];
        String endTimeStr = date + " " + arr[1];
        if (Objects.isNull(DateDisposeUtils.parseDate(startTimeStr)) || Objects.isNull(DateDisposeUtils.parseDate(endTimeStr))) {
            throw ExceptionResultEnum.ERROR.exception(
                    "提供的日期时间格式异常(考试日期列应使用文本格式) \n考试日期[" + date + "]应该使用如下的格式（考试日期:2021-09-01 或 2021/09/01 或 2021.09.01）"
                            + "\n考试时间[" + time + "]应该使用如下的格式（考试时间:18:00-20:00）");
        }
        long startTime = DateDisposeUtils.parseDate(startTimeStr).getTime();
        long endTime = DateDisposeUtils.parseDate(endTimeStr).getTime();
        if (!SystemConstant.longNotNull(startTime) || !SystemConstant.longNotNull(endTime)) {
            throw ExceptionResultEnum.ERROR.exception(
                    "提供的日期时间格式异常(考试日期列应使用文本格式) \n考试日期[" + date + "]应该使用如下的格式（考试日期:2021-09-01 或 2021/09/01 或 2021.09.01）"
                            + "\n考试时间[" + time + "]应该使用如下的格式（考试时间:18:00-20:00）");
        }
        if (startTime >= endTime) {
            throw ExceptionResultEnum.ERROR.exception("开始时间要小于结束时间 时间： " + time);
        }

        Map<String, Object> timeMap = new HashMap<>();
        timeMap.put("startTime", startTime);
        timeMap.put("endTime", endTime);
        return timeMap;
    }

    /**
     * 根据开始时间和结束时间解析日期和时间
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     * @return 键值对
     */
    public static Map<String, Object> analyzeDateAndTime(Long startTime, Long endTime) {
        Map<String, Object> map = new HashMap<>();
        if (startTime != null && startTime > 0 && endTime != null && endTime > 0) {
            String tmpDateStart;
            String tmpDateEnd;
            String tmpTimeStart;
            String tmpTimeEnd;
            String date;
            String time;

            Calendar calendar = Calendar.getInstance();
            calendar.setTime(new Date(startTime));
            tmpDateStart = (new SimpleDateFormat("yyyy-MM-dd")).format(calendar.getTime());
            tmpTimeStart = (new SimpleDateFormat("HH:mm:ss")).format(calendar.getTime());

            calendar.setTime(new Date(endTime));
            tmpDateEnd = (new SimpleDateFormat("yyyy-MM-dd")).format(calendar.getTime());
            tmpTimeEnd = (new SimpleDateFormat("HH:mm:ss")).format(calendar.getTime());

            if (!tmpDateStart.equals(tmpDateEnd)) {
                throw ExceptionResultEnum.ERROR.exception("开始时间和结束时间不在同一天");
            }

            date = tmpDateStart;
            time = tmpTimeStart + " - " + tmpTimeEnd;
            map.put("date", date);
            map.put("time", time);
        } else {
            map.put("date", "-");
            map.put("time", "-");
        }

        return map;
    }

    /**
     * 根据开始时间和结束时间解析日期和时间
     *
     * @param startTime 开始时间
     * @param endTime   结束时间
     */
    public static void analyzeAndCompareDateAndTime(Long startTime, Long endTime) {
        String tmpDateStart;
        String tmpDateEnd;

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date(startTime));
        tmpDateStart = (new SimpleDateFormat("yyyy-MM-dd")).format(calendar.getTime());

        calendar.setTime(new Date(endTime));
        tmpDateEnd = (new SimpleDateFormat("yyyy-MM-dd")).format(calendar.getTime());

        if (!tmpDateStart.equals(tmpDateEnd)) {
            throw ExceptionResultEnum.ERROR.exception("开始时间和结束时间不在同一天");
        }

        if (startTime >= endTime) {
            throw ExceptionResultEnum.ERROR.exception("开始时间必须小于结束时间");
        }
    }

    /**
     * 解析excel
     *
     * @param inputStream   inputStream
     * @param clazz         实体类映射对象
     * @param fillError     是否为映射对象添加错误信息（如果要添加的话映射对象的属性必须有一个字段有@ExcelError注解）
     * @param columnNameRow 从第几行开始
     * @throws Exception 异常
     */
    public static <T> ExcelResult<T> analyzeExcel(InputStream inputStream, Class<T> clazz, boolean fillError, int columnNameRow)
            throws Exception {
        boolean analyzeResult = true;
        ExcelReader excelReader = ExcelReader.create(ExcelType.XLSX, inputStream, columnNameRow);
        List<String[]> excelDatasource = excelReader.getDataArrayList();
        List<T> dtoList = new ArrayList<>();
        Map<Integer, String> errorMap = new HashMap<>();
        AtomicInteger rowIndex = new AtomicInteger(columnNameRow + 1);
        if (CollectionUtils.isNotEmpty(excelDatasource)) {
            List<String> columnNameList = Arrays.asList(excelReader.getColumnNames());
            Field[] fields = clazz.getDeclaredFields();
            Field errorField = null;
            if (fillError) {
                for (Field field : fields) {
                    ExcelError annotation = field.getAnnotation(ExcelError.class);
                    if (Objects.nonNull(annotation)) {
                        errorField = field;
                        break;
                    }
                }
            }
            // 要导入的字段
            List<Field> fieldList = Arrays.stream(fields).filter(field -> {
                ExcelColumn annotation = field.getAnnotation(ExcelColumn.class);
                return Objects.nonNull(annotation);
            }).sorted(Comparator.comparingInt(o -> o.getAnnotation(ExcelColumn.class).index())).collect(Collectors.toList());
            List<String> fieldNameList = fieldList.stream().map(e -> e.getAnnotation(ExcelColumn.class).name()).collect(Collectors.toList());

            // 表头判断
            if (columnNameList.size() != fieldNameList.size()) {
                throw ExceptionResultEnum.ERROR.exception(String.format("表头错误,应为[%s]", String.join(";", fieldNameList)));
            }
            for (int i = 0; i < columnNameList.size(); i++) {
                if (!Objects.equals(columnNameList.get(i), fieldNameList.get(i))) {
                    throw ExceptionResultEnum.ERROR.exception(
                            String.format("表头错误,应为[%s]", String.join(";", fieldNameList)));
                }
            }

            // 解析
            for (String[] cell : excelDatasource) {
                T dto = clazz.newInstance();
                int index = rowIndex.incrementAndGet();
                boolean emptyCell = true;
                List<String> excelErrorList = new ArrayList<>();
                for (int i = 0; i < cell.length; i++) {
                    String v = StringUtils.trimToNull(cell[i]);
                    Field field = fieldList.get(i);
                    ExcelColumn annotation = field.getAnnotation(ExcelColumn.class);
                    if (SystemConstant.strNotNull(v)) {
                        field.setAccessible(true);
                        try {
                            Object cv = convertAttributeValue(field.getType().getSimpleName(), v);
                            field.set(dto, cv);
                            emptyCell = false;
                        } catch (Exception e) {
                            excelErrorList.add(String.format("[%s]格式错误: %s", annotation.name(), e.getMessage()));
                        }
                    } else if (annotation.nullable()) {
                        excelErrorList.add(String.format("[%s]必填", annotation.name()));
                    }
                }
                if (emptyCell) {
                    // 舍弃全空行
                    continue;
                }
                if (CollectionUtils.isNotEmpty(excelErrorList)) {
                    // 有异常数据
                    analyzeResult = false;
                    String errorMsg = String.format("第[%s]行,%s", index, String.join(",", excelErrorList));
                    if (fillError && Objects.nonNull(errorField)) {
                        errorField.setAccessible(true);
                        errorField.set(dto, convertAttributeValue(errorField.getType().getSimpleName(), errorMsg));
                    }
                    errorMap.put(index, errorMsg);
                }
                dtoList.add(dto);
            }
        }
        ExcelResult<T> excelResult = new ExcelResult<>();
        excelResult.setSuccess(analyzeResult);
        excelResult.setDatasource(dtoList);
        excelResult.setErrorMap(errorMap);
        return excelResult;
    }

    // 定义支持的数据类型枚举
    private enum DataType {
        INT, DOUBLE, LONG, FLOAT, DATE, STRING
    }

    /**
     * 将属性的值从String类型转换成他们各自对应的数据类型的值
     *
     * @param type  属性的类型
     * @param value 属性的值
     * @return 对应数据类型的值
     */
    private static Object convertAttributeValue(String type, String value) {
        // 使用枚举来处理数据类型
        DataType dataType = getDataType(type);

        switch (dataType) {
            case INT:
                return parseInt(value);
            case DOUBLE:
                return parseDouble(value);
            case LONG:
                return parseLong(value);
            case FLOAT:
                return parseFloat(value);
            case DATE:
                return parseDate(value);
            default:
                return value;
        }
    }

    /**
     * 获取属性的类型枚举
     *
     * @param type 属性类型名称
     * @return 属性的类型对应的枚举值
     */
    private static DataType getDataType(String type) {
        switch (type.toLowerCase()) {
            case "int":
            case "integer":
                return DataType.INT;
            case "double":
                return DataType.DOUBLE;
            case "long":
                return DataType.LONG;
            case "float":
                return DataType.FLOAT;
            case "date":
                return DataType.DATE;
            case "string":
                return DataType.STRING;
            default:
                return DataType.valueOf(type); // 对于未知类型，尝试直接匹配枚举值
        }
    }

    /**
     * 将字符串转换为整数
     *
     * @param value 字符串值
     * @return 整数值
     */
    private static int parseInt(String value) {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            throw ExceptionResultEnum.EXCEPTION_ERROR.exception("只能用小写整数");
        }
    }

    /**
     * 将字符串转换为双精度浮点数
     *
     * @param value 字符串值
     * @return 双精度浮点数值
     */
    private static double parseDouble(String value) {
        try {
            return Double.parseDouble(value);
        } catch (NumberFormatException e) {
            throw ExceptionResultEnum.ERROR.exception("只能用数字");
        }
    }

    /**
     * 将字符串转换为长整数
     *
     * @param value 字符串值
     * @return 长整数值
     */
    private static long parseLong(String value) {
        try {
            return Long.parseLong(value);
        } catch (NumberFormatException e) {
            throw ExceptionResultEnum.ERROR.exception("只能用数字");
        }
    }

    /**
     * 将字符串转换为长整数
     *
     * @param value 字符串值
     * @return 长整数值
     */
    private static float parseFloat(String value) {
        try {
            return Float.parseFloat(value);
        } catch (NumberFormatException e) {
            throw ExceptionResultEnum.ERROR.exception("只能用数字");
        }
    }

    /**
     * 将字符串转换为日期
     *
     * @param value 字符串值
     * @return 日期值
     */
    private static Date parseDate(String value) {
        // 使用 Java 8 的日期时间 API
        DateTimeFormatter formatter;
        Pattern datePattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2}");
        Pattern datetimePattern = Pattern.compile("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}");

        Matcher dateMatcher = datePattern.matcher(value);
        Matcher datetimeMatcher = datetimePattern.matcher(value);

        if (dateMatcher.matches()) {
            formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        } else if (datetimeMatcher.matches()) {
            formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        } else {
            return new Date();
        }

        LocalDateTime dateTime = LocalDateTime.parse(value, formatter);
        return Date.from(dateTime.atZone(java.time.ZoneId.systemDefault()).toInstant());
    }

    public static void delFolder(String folderPath) {
        try {
            delAllFile(folderPath); //删除完里面所有内容
            String filePath = folderPath;
            filePath = filePath.toString();
            File myFilePath = new File(filePath);
            myFilePath.delete(); //删除空文件夹
        } catch (Exception e) {
            log.error(SystemConstant.LOG_ERROR, e);
        }
    }

    public static void delAllFile(String path) {
        boolean flag = false;
        File file = new File(path);
        if (!file.exists()) {
            return;
        }
        if (!file.isDirectory()) {
            return;
        }
        String[] tempList = file.list();
        File temp = null;
        for (int i = 0; i < Objects.requireNonNull(tempList).length; i++) {
            if (path.endsWith(File.separator)) {
                temp = new File(path + tempList[i]);
            } else {
                temp = new File(path + File.separator + tempList[i]);
            }
            if (temp.isFile()) {
                temp.delete();
            }
            if (temp.isDirectory()) {
                delAllFile(path + SystemConstant.ORG_SPLIT + tempList[i]);//先删除文件夹里面的文件
                delFolder(path + SystemConstant.ORG_SPLIT + tempList[i]);//再删除空文件夹
                flag = true;
            }
        }
    }

    public static void outputFile(HttpServletResponse response, InputStream inputStream, String fileName) {
        try {
            //            byte[] buf = new byte[1024];
            //            int len = 0;

            String fName = new String(fileName.getBytes(), "ISO-8859-1");

            response.reset();
            response.setContentType("application/x-msdownload");
            response.setHeader("Content-Disposition", "attachment; filename=" + fName);

            //            OutputStream outStream = response.getOutputStream();

            //            while ((len = inputStream.read(buf)) > 0) {
            //                outStream.write(buf, 0, len);
            //            }

            IOUtils.copy(inputStream, response.getOutputStream());
            inputStream.close();
            //            outStream.close();
        } catch (IOException e) {
            log.error(SystemConstant.LOG_ERROR, e);
        }
    }

    public static void outputFile(HttpServletResponse response, ByteArrayOutputStream baos, String fileName) throws Exception {
        try {
            String fName = URLEncoder.encode(fileName, SystemConstant.CHARSET_NAME);

            response.reset();
            response.setContentType("application/x-msdownload");
            response.setHeader("Content-Disposition", "attachment; filename=" + fName);

            //            OutputStream outStream = response.getOutputStream();
            //            baos.writeTo(outStream);
            //            outStream.close();
            IOUtils.write(baos.toByteArray(), response.getOutputStream());
        } catch (IOException e) {
            throw new Exception("下载失败");
        }
    }

    public static List<File> pdfToImageFile(String sourcePath, String targetPath) throws Exception {
        PDDocument doc = null;
        ByteArrayOutputStream os = null;
        InputStream stream = null;
        //        OutputStream out = null;
        List<File> fileList = new ArrayList<>();
        try {
            // pdf路径
            stream = new FileInputStream(sourcePath);
            // 加载解析PDF文件
            doc = PDDocument.load(stream);
            PDFRenderer pdfRenderer = new PDFRenderer(doc);
            PDPageTree pages = doc.getPages();
            int pageCount = pages.getCount();
            for (int i = 0; i < pageCount; i++) {
                BufferedImage bim = pdfRenderer.renderImageWithDPI(i, 200);
                os = new ByteArrayOutputStream();
                ImageIO.write(bim, "jpg", os);
                // jpg文件转出路径
                File file = new File(targetPath + SystemConstant.HYPHEN + (i + 1) + SystemConstant.JPG_PREFIX);
                if (!file.getParentFile().exists()) {
                    // 不存在则创建父目录及子文件
                    file.getParentFile().mkdirs();
                    file.createNewFile();
                }
                //                out = new FileOutputStream(file);
                IOUtils.write(os.toByteArray(), new FileOutputStream(file));
                //                out.write(dataList);
                fileList.add(file);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        } finally {
            if (doc != null) {
                doc.close();
            }
            if (os != null) {
                os.close();
            }
            if (stream != null) {
                stream.close();
            }
        }
        return fileList;
    }

    public static List<File> pdfToImageFile(File pdfFile) throws Exception {
        PDDocument doc = null;
        ByteArrayOutputStream os = null;
        InputStream stream = null;
        List<File> fileList = new ArrayList<>();
        try {
            // pdf路径
            stream = new FileInputStream(pdfFile);
            // 加载解析PDF文件
            doc = PDDocument.load(stream);
            PDFRenderer pdfRenderer = new PDFRenderer(doc);
            PDPageTree pages = doc.getPages();
            int pageCount = pages.getCount();
            for (int i = 0; i < pageCount; i++) {
                BufferedImage bim = pdfRenderer.renderImageWithDPI(i, 200);
                os = new ByteArrayOutputStream();
                ImageIO.write(bim, "jpg", os);
                // jpg文件转出路径
                File file = SystemConstant.getFileTempVar(SystemConstant.JPG_PREFIX);
                if (!file.getParentFile().exists()) {
                    // 不存在则创建父目录及子文件
                    file.getParentFile().mkdirs();
                    file.createNewFile();
                }
                IOUtils.write(os.toByteArray(), new FileOutputStream(file));
                fileList.add(file);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        } finally {
            if (doc != null) {
                doc.close();
            }
            if (os != null) {
                os.close();
            }
            if (stream != null) {
                stream.close();
            }
        }
        return fileList;
    }
}
