package com.gxwebsoft.credit.controller;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.ss.usermodel.Hyperlink;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.InputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * Excel 导入导出通用支持
 */
public class ExcelImportSupport {

    public static class ImportResult<T> {
        private final List<T> data;
        private final int titleRows;
        private final int headRows;
        private final int sheetIndex;

        public ImportResult(List<T> data, int titleRows, int headRows) {
            this(data, titleRows, headRows, 0);
        }

        public ImportResult(List<T> data, int titleRows, int headRows, int sheetIndex) {
            this.data = data;
            this.titleRows = titleRows;
            this.headRows = headRows;
            this.sheetIndex = sheetIndex;
        }

        public List<T> getData() {
            return data;
        }

        public int getTitleRows() {
            return titleRows;
        }

        public int getHeadRows() {
            return headRows;
        }

        public int getSheetIndex() {
            return sheetIndex;
        }
    }

    public static <T> ImportResult<T> read(MultipartFile file, Class<T> clazz, Predicate<T> emptyRowPredicate) throws Exception {
        return read(file, clazz, emptyRowPredicate, 0);
    }

    /**
     * 自动尝试所有 sheet（从第 0 个开始），直到读取到非空数据。
     */
    public static <T> ImportResult<T> readAnySheet(MultipartFile file, Class<T> clazz, Predicate<T> emptyRowPredicate) throws Exception {
        ImportResult<T> result = read(file, clazz, emptyRowPredicate, 0);
        if (!CollectionUtils.isEmpty(result.getData())) {
            return result;
        }

        int sheetCount = getSheetCount(file);
        for (int i = 1; i < sheetCount; i++) {
            ImportResult<T> sheetResult = read(file, clazz, emptyRowPredicate, i);
            if (!CollectionUtils.isEmpty(sheetResult.getData())) {
                return sheetResult;
            }
        }
        return result;
    }

    /**
     * 自动尝试所有 sheet（从第 0 个开始），并在每个 sheet 内选择“得分”最高的表头配置。
     */
    public static <T> ImportResult<T> readAnySheetBest(MultipartFile file, Class<T> clazz, Predicate<T> emptyRowPredicate, Predicate<T> scoreRowPredicate) throws Exception {
        ImportResult<T> result = readBest(file, clazz, emptyRowPredicate, scoreRowPredicate, 0);
        if (!CollectionUtils.isEmpty(result.getData())) {
            return result;
        }

        int sheetCount = getSheetCount(file);
        for (int i = 1; i < sheetCount; i++) {
            ImportResult<T> sheetResult = readBest(file, clazz, emptyRowPredicate, scoreRowPredicate, i);
            if (!CollectionUtils.isEmpty(sheetResult.getData())) {
                return sheetResult;
            }
        }
        return result;
    }

    /**
     * 读取指定 sheet 的 Excel。
     *
     * @param sheetIndex 目标 sheet 下标，从 0 开始。第二个选项卡传 1。
     */
    public static <T> ImportResult<T> read(MultipartFile file, Class<T> clazz, Predicate<T> emptyRowPredicate, int sheetIndex) throws Exception {
        List<T> list = null;
        int usedTitleRows = 0;
        int usedHeadRows = 0;
        int[][] tryConfigs = new int[][]{{1, 1}, {0, 1}, {2, 1}, {3, 1}, {0, 2}, {0, 3}, {0, 4}};
        Exception lastFailure = null;
        boolean anyImported = false;

        for (int[] config : tryConfigs) {
            try {
                list = filterEmptyRows(importSheet(file, clazz, config[0], config[1], sheetIndex), emptyRowPredicate);
                anyImported = true;
            } catch (Exception e) {
                // Some source files can trigger easypoi/POI runtime exceptions for certain title/head row combinations.
                // Keep trying other configurations instead of failing the whole import.
                lastFailure = e;
                continue;
            }
            if (!CollectionUtils.isEmpty(list)) {
                usedTitleRows = config[0];
                usedHeadRows = config[1];
                break;
            }
        }

        // Fallback for upstream files with extra banner/title rows or wider multi-row headers.
        // Keep the default fast path above for common templates.
        if (CollectionUtils.isEmpty(list)) {
            final int maxTitleRows = 10;
            final int maxHeadRows = 6;
            outer:
            for (int titleRows = 0; titleRows <= maxTitleRows; titleRows++) {
                for (int headRows = 1; headRows <= maxHeadRows; headRows++) {
                    try {
                        list = filterEmptyRows(importSheet(file, clazz, titleRows, headRows, sheetIndex), emptyRowPredicate);
                        anyImported = true;
                    } catch (Exception e) {
                        lastFailure = e;
                        continue;
                    }
                    if (!CollectionUtils.isEmpty(list)) {
                        usedTitleRows = titleRows;
                        usedHeadRows = headRows;
                        break outer;
                    }
                }
            }
        }
        if (list == null) {
            if (!anyImported && lastFailure != null) {
                throw lastFailure;
            }
            list = Collections.emptyList();
        }
        return new ImportResult<>(list, usedTitleRows, usedHeadRows, sheetIndex);
    }

    /**
     * 读取指定 sheet 的 Excel，并从多组表头配置中挑选“得分”最高的结果。
     */
    public static <T> ImportResult<T> readBest(MultipartFile file, Class<T> clazz, Predicate<T> emptyRowPredicate, Predicate<T> scoreRowPredicate, int sheetIndex) throws Exception {
        List<T> bestList = null;
        int bestTitleRows = 0;
        int bestHeadRows = 0;
        int bestScore = -1;
        int bestSize = -1;
        Exception lastFailure = null;
        boolean anyImported = false;

        int[][] tryConfigs = new int[][]{{1, 1}, {0, 1}, {2, 1}, {3, 1}, {0, 2}, {0, 3}, {0, 4}, {1, 2}, {1, 3}, {1, 4}, {2, 2}, {2, 3}, {2, 4}, {3, 2}, {3, 3}, {3, 4}};

        for (int[] config : tryConfigs) {
            List<T> list;
            try {
                list = filterEmptyRows(importSheet(file, clazz, config[0], config[1], sheetIndex), emptyRowPredicate);
                anyImported = true;
            } catch (Exception e) {
                lastFailure = e;
                continue;
            }
            if (CollectionUtils.isEmpty(list)) {
                continue;
            }

            int score = 0;
            if (scoreRowPredicate != null) {
                for (T row : list) {
                    if (scoreRowPredicate.test(row)) {
                        score++;
                    }
                }
            }

            int size = list.size();
            if (score > bestScore || (score == bestScore && size > bestSize)) {
                bestList = list;
                bestTitleRows = config[0];
                bestHeadRows = config[1];
                bestScore = score;
                bestSize = size;
            }
        }

        // Fallback scan: broaden the search space for messy source files (extra title rows, multi-row headers).
        if (bestList == null) {
            final int maxTitleRows = 10;
            final int maxHeadRows = 6;
            for (int titleRows = 0; titleRows <= maxTitleRows; titleRows++) {
                for (int headRows = 1; headRows <= maxHeadRows; headRows++) {
                    List<T> list;
                    try {
                        list = filterEmptyRows(importSheet(file, clazz, titleRows, headRows, sheetIndex), emptyRowPredicate);
                        anyImported = true;
                    } catch (Exception e) {
                        lastFailure = e;
                        continue;
                    }
                    if (CollectionUtils.isEmpty(list)) {
                        continue;
                    }

                    int score = 0;
                    if (scoreRowPredicate != null) {
                        for (T row : list) {
                            if (scoreRowPredicate.test(row)) {
                                score++;
                            }
                        }
                    }

                    int size = list.size();
                    if (score > bestScore || (score == bestScore && size > bestSize)) {
                        bestList = list;
                        bestTitleRows = titleRows;
                        bestHeadRows = headRows;
                        bestScore = score;
                        bestSize = size;
                    }
                }
            }
        }

        if (bestList != null) {
            return new ImportResult<>(bestList, bestTitleRows, bestHeadRows, sheetIndex);
        }

        if (!anyImported && lastFailure != null) {
            throw lastFailure;
        }
        return read(file, clazz, emptyRowPredicate, sheetIndex);
    }

    private static <T> List<T> importSheet(MultipartFile file, Class<T> clazz, int titleRows, int headRows, int sheetIndex) throws Exception {
        ImportParams importParams = new ImportParams();
        importParams.setTitleRows(titleRows);
        importParams.setHeadRows(headRows);
        importParams.setStartSheetIndex(sheetIndex);
        importParams.setSheetNum(1);
        return ExcelImportUtil.importExcel(file.getInputStream(), clazz, importParams);
    }

    private static <T> List<T> filterEmptyRows(List<T> rawList, Predicate<T> emptyRowPredicate) {
        if (CollectionUtils.isEmpty(rawList)) {
            return rawList;
        }
        rawList.removeIf(emptyRowPredicate);
        return rawList;
    }

    private static int getSheetCount(MultipartFile file) throws Exception {
        try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is)) {
            return workbook.getNumberOfSheets();
        }
    }

    /**
     * 根据 sheet 名称查找下标（优先精确匹配，其次前缀匹配/包含匹配）。
     *
     * @return 找不到返回 -1
     */
    public static int findSheetIndex(MultipartFile file, String sheetName) throws Exception {
        if (file == null || sheetName == null || sheetName.trim().isEmpty()) {
            return -1;
        }
        String target = normalizeSheetName(sheetName);
        if (target.isEmpty()) {
            return -1;
        }

        try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is)) {
            int sheetCount = workbook.getNumberOfSheets();
            for (int i = 0; i < sheetCount; i++) {
                String candidate = normalizeSheetName(workbook.getSheetName(i));
                if (Objects.equals(candidate, target)) {
                    return i;
                }
            }
            for (int i = 0; i < sheetCount; i++) {
                String candidate = normalizeSheetName(workbook.getSheetName(i));
                if (candidate.startsWith(target) || candidate.contains(target) || target.startsWith(candidate)) {
                    return i;
                }
            }
            return -1;
        }
    }

    public static int findSheetIndex(MultipartFile file, String sheetName, int defaultIndex) throws Exception {
        int idx = findSheetIndex(file, sheetName);
        return idx >= 0 ? idx : defaultIndex;
    }

    private static String normalizeSheetName(String sheetName) {
        if (sheetName == null) {
            return "";
        }
        return sheetName.replace(" ", "").replace("　", "").trim();
    }

    /**
     * 读取指定列（由表头名定位）的超链接，返回：单元格显示值 -> 超链接地址。
     *
     * <p>适用于：导入对象没有 url 字段，但 Excel 把链接放在某个“名称/案号”等列的超链接里。</p>
     */
    public static Map<String, String> readHyperlinksByHeaderKey(MultipartFile file, int sheetIndex, int titleRows, int headRows, String headerName) throws Exception {
        if (file == null || headerName == null || headerName.trim().isEmpty()) {
            return Collections.emptyMap();
        }

        try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is)) {
            if (workbook.getNumberOfSheets() <= sheetIndex) {
                return Collections.emptyMap();
            }
            Sheet sheet = workbook.getSheetAt(sheetIndex);
            if (sheet == null) {
                return Collections.emptyMap();
            }

            int colIndex = findColumnIndexByHeader(sheet, titleRows, headRows, headerName);
            if (colIndex < 0) {
                return Collections.emptyMap();
            }

            Map<String, String> result = new HashMap<>();
            DataFormatter formatter = new DataFormatter();
            int dataStartRow = titleRows + headRows;
            for (int r = dataStartRow; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) {
                    continue;
                }
                Cell cell = row.getCell(colIndex);
                if (cell == null) {
                    continue;
                }
                String address = extractHyperlinkAddress(cell);
                if (address == null || address.isEmpty()) {
                    continue;
                }
                String key = formatter.formatCellValue(cell);
                if (key == null || key.trim().isEmpty()) {
                    continue;
                }
                result.put(key.trim(), address);
            }
            return result;
        }
    }

    /**
     * 读取两列（由表头名定位）的文本/超链接，返回：key列显示值 -> value列（优先超链接地址，其次单元格文本）。
     *
     * <p>适用于：Excel 把 url 放在单独一列（可能是纯文本，也可能是超链接）。</p>
     */
    public static Map<String, String> readKeyValueByHeaders(MultipartFile file,
                                                            int sheetIndex,
                                                            int titleRows,
                                                            int headRows,
                                                            String keyHeaderName,
                                                            String valueHeaderName) throws Exception {
        if (file == null
            || keyHeaderName == null || keyHeaderName.trim().isEmpty()
            || valueHeaderName == null || valueHeaderName.trim().isEmpty()) {
            return Collections.emptyMap();
        }

        try (InputStream is = file.getInputStream(); Workbook workbook = WorkbookFactory.create(is)) {
            if (workbook.getNumberOfSheets() <= sheetIndex) {
                return Collections.emptyMap();
            }
            Sheet sheet = workbook.getSheetAt(sheetIndex);
            if (sheet == null) {
                return Collections.emptyMap();
            }

            int keyCol = findColumnIndexByHeader(sheet, titleRows, headRows, keyHeaderName);
            int valCol = findColumnIndexByHeader(sheet, titleRows, headRows, valueHeaderName);
            if (keyCol < 0 || valCol < 0) {
                return Collections.emptyMap();
            }

            Map<String, String> result = new HashMap<>();
            DataFormatter formatter = new DataFormatter();
            int dataStartRow = titleRows + headRows;
            for (int r = dataStartRow; r <= sheet.getLastRowNum(); r++) {
                Row row = sheet.getRow(r);
                if (row == null) {
                    continue;
                }
                Cell keyCell = row.getCell(keyCol);
                if (keyCell == null) {
                    continue;
                }
                String key = formatter.formatCellValue(keyCell);
                if (key == null || key.trim().isEmpty()) {
                    continue;
                }

                Cell valCell = row.getCell(valCol);
                if (valCell == null) {
                    continue;
                }
                String value = extractHyperlinkAddress(valCell);
                if (value == null || value.trim().isEmpty()) {
                    value = formatter.formatCellValue(valCell);
                }
                if (value == null || value.trim().isEmpty()) {
                    continue;
                }
                result.put(key.trim(), value.trim());
            }
            return result;
        }
    }

    /**
     * 读取“key列 -> url”的映射：
     * - 优先读取 key 列自身的超链接（url 常挂在名称/案号等列的超链接里）
     * - 如果源文件提供独立的 url/URL/网址/链接/链接地址 等列，则读取该列（支持文本或超链接）
     */
    public static Map<String, String> readUrlByKey(MultipartFile file,
                                                   int sheetIndex,
                                                   int titleRows,
                                                   int headRows,
                                                   String keyHeaderName) throws Exception {
        if (file == null || keyHeaderName == null || keyHeaderName.trim().isEmpty()) {
            return Collections.emptyMap();
        }

        Map<String, String> result = new HashMap<>();

        // 1) url 挂在 key 列超链接里（最常见）
        Map<String, String> fromKeyHyperlinks = readHyperlinksByHeaderKey(file, sheetIndex, titleRows, headRows, keyHeaderName);
        if (!fromKeyHyperlinks.isEmpty()) {
            result.putAll(fromKeyHyperlinks);
        }

        // 2) url 作为单独一列（多种表头命名）
        String[] urlHeaders = new String[]{"url", "URL", "网址", "链接", "链接地址"};
        for (String urlHeader : urlHeaders) {
            Map<String, String> fromUrlCol = readKeyValueByHeaders(file, sheetIndex, titleRows, headRows, keyHeaderName, urlHeader);
            if (fromUrlCol.isEmpty()) {
                continue;
            }
            for (Map.Entry<String, String> entry : fromUrlCol.entrySet()) {
                // 不覆盖已从 key 超链接读取到的地址
                result.putIfAbsent(entry.getKey(), entry.getValue());
            }
        }
        return result;
    }

    private static int findColumnIndexByHeader(Sheet sheet, int titleRows, int headRows, String headerName) {
        int firstHeaderRow = Math.max(0, titleRows);
        int lastHeaderRow = Math.max(0, titleRows + headRows - 1);
        for (int r = firstHeaderRow; r <= lastHeaderRow; r++) {
            Row row = sheet.getRow(r);
            if (row == null) {
                continue;
            }
            for (int c = row.getFirstCellNum(); c < row.getLastCellNum(); c++) {
                Cell cell = row.getCell(c);
                if (cell == null) {
                    continue;
                }
                if (cell.getCellType() == CellType.STRING) {
                    String value = cell.getStringCellValue();
                    if (headerName.equals(value != null ? value.trim() : null)) {
                        return c;
                    }
                } else {
                    DataFormatter formatter = new DataFormatter();
                    String value = formatter.formatCellValue(cell);
                    if (headerName.equals(value != null ? value.trim() : null)) {
                        return c;
                    }
                }
            }
        }
        return -1;
    }

    private static String extractHyperlinkAddress(Cell cell) {
        Hyperlink hyperlink = cell.getHyperlink();
        if (hyperlink != null && hyperlink.getAddress() != null && !hyperlink.getAddress().isEmpty()) {
            return hyperlink.getAddress();
        }
        if (cell.getCellType() == CellType.FORMULA) {
            String formula = cell.getCellFormula();
            if (formula != null && formula.toUpperCase().startsWith("HYPERLINK(")) {
                int firstQuote = formula.indexOf('\"');
                if (firstQuote >= 0) {
                    int secondQuote = formula.indexOf('\"', firstQuote + 1);
                    if (secondQuote > firstQuote) {
                        return formula.substring(firstQuote + 1, secondQuote);
                    }
                }
            }
        }
        return null;
    }

    public static <T> Workbook buildTemplate(String title, String sheetName, Class<T> clazz, List<T> examples) {
        ExportParams exportParams = new ExportParams(title, sheetName);
        return ExcelExportUtil.exportExcel(exportParams, clazz, examples);
    }
}
