package com.hotwj.platform.securityManagement.training.service.impl;

import cn.hutool.core.io.resource.ClassPathResource;
import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcel;
import cn.idev.excel.write.metadata.WriteSheet;
import cn.idev.excel.write.metadata.fill.FillConfig;
import cn.idev.excel.write.metadata.fill.FillWrapper;
import com.hotwj.platform.driverManagement.driver.domain.vo.HotDriverVo;
import com.hotwj.platform.driverManagement.driver.service.IHotDriverService;
import com.hotwj.platform.resourceManagement.company.domain.vo.SysCompanyVo;
import com.hotwj.platform.resourceManagement.company.service.ISysCompanyService;
import com.hotwj.platform.securityManagement.training.service.IHotTrainingAnnualRecordExportService;
import com.hotwj.platform.securityManagement.trainingCourseRecord.domain.vo.PersonalAnnualRecordCourseVo;
import com.hotwj.platform.securityManagement.trainingCourseRecord.service.IHotTrainingCourseRecordService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

@Service
@RequiredArgsConstructor
public class HotTrainingAnnualRecordExportServiceImpl implements IHotTrainingAnnualRecordExportService {

    private static final String PERSONAL_ANNUAL_TEMPLATE_PATH = "excelTemplates/personalAnnualTrainingRecord.xlsx";
    private static final String PERSONAL_ANNUAL_POST_DEFAULT = "驾驶员/押运员";
    private static final String PERSONAL_ANNUAL_ZIP_NAME = "个人年度培训记录.zip";
    private static final String TRAINING_ORGANIZATION = "HOT交通安全管理清单平台";
    private static final String DEFAULT_COMPANY_NAME = "未知单位";
    private static final String DEFAULT_LEADER = "未知负责人";
    private static final DateTimeFormatter BIRTH_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
    private static final String ROW_HAS_COURSE_KEY = "hasCourse";

    private final IHotDriverService hotDriverService;
    private final IHotTrainingCourseRecordService hotTrainingCourseRecordService;
    private final DictService dictService;
    private final ISysCompanyService sysCompanyService;

    @Override
    public void exportPersonalAnnualRecord(String driverId, Integer year, String trainingType, HttpServletResponse response) {
        List<String> driverIds = parseDriverIds(driverId);
        int targetYear = year == null ? LocalDate.now().getYear() : year;
        Date startTime = Date.from(LocalDate.of(targetYear, 1, 1).atStartOfDay(ZoneId.systemDefault()).toInstant());
        Date endTime = Date.from(LocalDate.of(targetYear + 1, 1, 1).atStartOfDay(ZoneId.systemDefault()).toInstant());

        List<ExportDriverRecord> records = new ArrayList<>();
        for (String id : driverIds) {
            HotDriverVo driver = hotDriverService.queryById(id);
            validateDriver(id, driver);
            List<PersonalAnnualRecordCourseVo> courses = hotTrainingCourseRecordService.queryPersonalAnnualRecordCourses(id, startTime, endTime, trainingType);
            records.add(new ExportDriverRecord(driver, courses == null ? List.of() : courses));
        }

        try {
            if (records.size() == 1) {
                exportSingleExcel(records.get(0), response);
            } else {
                exportZip(records, response);
            }
        } catch (IOException e) {
            throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "导出失败，请稍后重试", e);
        }
    }

    private void exportSingleExcel(ExportDriverRecord record, HttpServletResponse response) throws IOException {
        String filename = buildExcelFileName(record.driver());
        byte[] content = buildExcelBytes(record);
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        FileUtils.setAttachmentResponseHeader(response, filename);
        try (OutputStream outputStream = response.getOutputStream()) {
            outputStream.write(content);
            outputStream.flush();
        }
    }

    private void exportZip(List<ExportDriverRecord> records, HttpServletResponse response) throws IOException {
        response.setContentType("application/zip");
        FileUtils.setAttachmentResponseHeader(response, PERSONAL_ANNUAL_ZIP_NAME);
        try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
            for (ExportDriverRecord record : records) {
                byte[] content = buildExcelBytes(record);
                ZipEntry entry = new ZipEntry(buildExcelFileName(record.driver()));
                zos.putNextEntry(entry);
                zos.write(content);
                zos.closeEntry();
            }
            zos.finish();
        }
    }

    private byte[] buildExcelBytes(ExportDriverRecord record) throws IOException {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            Map<String, Object> data = new LinkedHashMap<>(buildTemplateData(record));
            Object rowsObj = data.remove("rows");
            Collection<?> rows = rowsObj instanceof Collection<?> collection ? collection : List.of();

            ClassPathResource templateResource = new ClassPathResource(PERSONAL_ANNUAL_TEMPLATE_PATH);
            ExcelWriter excelWriter = FastExcel.write(baos)
                    .withTemplate(templateResource.getStream())
                    .autoCloseStream(false)
                    .build();
            WriteSheet writeSheet = FastExcel.writerSheet().build();
            excelWriter.fill(data, writeSheet);
            FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
            excelWriter.fill(new FillWrapper("rows", rows), fillConfig, writeSheet);
            excelWriter.finish();
            return postProcessWorkbook(baos.toByteArray(), rows, data);
        }
    }

    private Map<String, Object> buildTemplateData(ExportDriverRecord record) {
        HotDriverVo driver = record.driver();
        SysCompanyVo company = resolveCompany(driver.getCompanyId());
        Map<String, Object> data = new LinkedHashMap<>();
        data.put("name", StringUtils.defaultString(driver.getName(), ""));
        data.put("gender", resolveGender(driver.getGender()));
        data.put("birthMonth", resolveBirthMonth(driver));
        data.put("educationLevel", resolveEducationLevel(driver.getEducation()));
        data.put("post", resolvePost(driver.getPersonType()));
        data.put("dutyPost", "");
        data.put("certificateObtainTime", "");
        data.put("companyName", resolveCompanyName(company));
        data.put("leader", resolveCompanyLeader(company));
        data.put("filler", "");

        List<Map<String, Object>> rows = new ArrayList<>();
        for (PersonalAnnualRecordCourseVo course : record.courses()) {
            Map<String, Object> row = new LinkedHashMap<>();
            row.put("trainingTime", StringUtils.defaultString(course.getTrainingTime(), ""));
            row.put("courseName", StringUtils.defaultString(course.getCourseName(), ""));
            row.put("evaluation", normalizeEvaluation(course.getEvaluation()));
            row.put("hours", StringUtils.defaultString(course.getHours(), ""));
            row.put("organization", TRAINING_ORGANIZATION);
            row.put("remark", "");
            row.put(ROW_HAS_COURSE_KEY, Boolean.TRUE);
            rows.add(row);
        }
        if (rows.isEmpty()) {
            Map<String, Object> emptyRow = new LinkedHashMap<>();
            emptyRow.put("trainingTime", "");
            emptyRow.put("courseName", "");
            emptyRow.put("evaluation", "");
            emptyRow.put("hours", "");
            emptyRow.put("organization", "");
            emptyRow.put("remark", "");
            emptyRow.put(ROW_HAS_COURSE_KEY, Boolean.FALSE);
            rows.add(emptyRow);
        }
        data.put("rows", rows);
        return data;
    }

    private String resolveGender(String gender) {
        if (StringUtils.isBlank(gender)) {
            return "";
        }
        return switch (gender) {
            case "0" -> "男";
            case "1" -> "女";
            default -> gender;
        };
    }

    private String resolveEducationLevel(String education) {
        if (StringUtils.isBlank(education)) {
            return "";
        }
        String label = dictService.getDictLabel("hot_academic", education);
        return StringUtils.isNotBlank(label) ? label : education;
    }

    private String formatBirthDate(Date date) {
        if (date == null) {
            return "";
        }
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().format(BIRTH_DATE_FORMATTER);
    }

    private String resolveBirthMonth(HotDriverVo driver) {
        String birth = formatBirthDate(driver.getBirthDate());
        if (StringUtils.isNotBlank(birth)) {
            return birth;
        }
        return parseBirthDateFromIdCard(driver.getIdCardNo());
    }

    private String parseBirthDateFromIdCard(String idCardNo) {
        if (StringUtils.isBlank(idCardNo)) {
            return "";
        }
        String raw = idCardNo.trim();
        try {
            if (raw.length() == 18) {
                String birthPart = raw.substring(6, 14);
                LocalDate birthDate = LocalDate.parse(birthPart, DateTimeFormatter.ofPattern("yyyyMMdd"));
                return birthDate.format(BIRTH_DATE_FORMATTER);
            }
            if (raw.length() == 15) {
                String birthPart = "19" + raw.substring(6, 12);
                LocalDate birthDate = LocalDate.parse(birthPart, DateTimeFormatter.ofPattern("yyyyMMdd"));
                return birthDate.format(BIRTH_DATE_FORMATTER);
            }
        } catch (Exception ignored) {
            // ignore invalid id card format and fallback to empty
        }
        return "";
    }

    private String normalizeEvaluation(String evaluation) {
        return "及格";
    }

    private String resolvePost(String personType) {
        if (StringUtils.isBlank(personType)) {
            return PERSONAL_ANNUAL_POST_DEFAULT;
        }
        String label = dictService.getDictLabel("hot_person_type", personType);
        if (StringUtils.isNotBlank(label)) {
            return label;
        }
        return personType;
    }

    private List<String> parseDriverIds(String driverId) {
        if (StringUtils.isBlank(driverId)) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "driverId不能为空");
        }
        Set<String> ids = new LinkedHashSet<>();
        for (String id : driverId.split(",")) {
            String trimmed = id == null ? "" : id.trim();
            if (StringUtils.isNotBlank(trimmed)) {
                ids.add(trimmed);
            }
        }
        if (ids.isEmpty()) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "driverId不能为空");
        }
        return new ArrayList<>(ids);
    }

    private void validateDriver(String driverId, HotDriverVo driver) {
        if (driver == null) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "无效driverId: " + driverId);
        }
        boolean enabled = Long.valueOf(1L).equals(driver.getStatus());
        boolean notDeleted = !Long.valueOf(1L).equals(driver.getIsDeleted());
        boolean auditPassed = driver.getAuditStatus() != null && driver.getAuditStatus() == 1;
        if (!enabled || !notDeleted || !auditPassed) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "driverId状态无效: " + driverId);
        }
    }

    private String buildExcelFileName(HotDriverVo driver) {
        String driverName = safeFilePart(StringUtils.defaultString(driver.getName(), "未知姓名"));
        return driverName + "年度培训记录.xlsx";
    }

    private String safeFilePart(String value) {
        return value.replaceAll("[\\\\/:*?\"<>|]", "_");
    }

    private SysCompanyVo resolveCompany(Long companyId) {
        if (companyId == null) {
            return null;
        }
        return sysCompanyService.queryById(companyId);
    }

    private String resolveCompanyName(SysCompanyVo company) {
        if (company == null || StringUtils.isBlank(company.getCompanyName())) {
            return DEFAULT_COMPANY_NAME;
        }
        return company.getCompanyName();
    }

    private String resolveCompanyLeader(SysCompanyVo company) {
        if (company == null) {
            return DEFAULT_LEADER;
        }
        if (StringUtils.isNotBlank(company.getResponsibleName())) {
            return company.getResponsibleName();
        }
        if (StringUtils.isNotBlank(company.getLegalPersonName())) {
            return company.getLegalPersonName();
        }
        return DEFAULT_LEADER;
    }

    private byte[] postProcessWorkbook(byte[] rawBytes, Collection<?> rows, Map<String, Object> data) throws IOException {
        try (XSSFWorkbook workbook = new XSSFWorkbook(new java.io.ByteArrayInputStream(rawBytes));
             ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            Sheet sheet = workbook.getSheetAt(0);
            replaceInlinePlaceholders(sheet, data);
            int dataStartRow = findDataStartRow(sheet);
            int rowCount = rows == null ? 0 : rows.size();
            if (dataStartRow >= 0 && rowCount > 0) {
                Row templateRow = sheet.getRow(dataStartRow);
                CellStyle contentTextStyle = buildContentTextStyle(workbook, templateRow);
                for (int i = 0; i < rowCount; i++) {
                    Row row = sheet.getRow(dataStartRow + i);
                    if (row == null) {
                        row = sheet.createRow(dataStartRow + i);
                    }
                    if (templateRow != null) {
                        row.setHeight(templateRow.getHeight());
                    }
                    copyTemplateStyles(templateRow, row);
                    setCellAsText(row, 1, getCellText(row, 1), contentTextStyle);
                    if (hasCourseRow(rows, i)) {
                        setCellValue(row, 3, TRAINING_ORGANIZATION);
                        setCellValue(row, 4, "及格");
                    }
                    mergeTrainingContentCells(sheet, dataStartRow + i);
                    alignRowCellsLeft(row, 0, 6);
                }
            }
            workbook.write(out);
            return out.toByteArray();
        }
    }

    private boolean hasCourseRow(Collection<?> rows, int index) {
        if (!(rows instanceof List<?> rowList) || index < 0 || index >= rowList.size()) {
            return false;
        }
        Object rowObj = rowList.get(index);
        if (!(rowObj instanceof Map<?, ?> rowMap)) {
            return false;
        }
        return Boolean.TRUE.equals(rowMap.get(ROW_HAS_COURSE_KEY));
    }

    private void replaceInlinePlaceholders(Sheet sheet, Map<String, Object> data) {
        if (data == null || data.isEmpty()) {
            return;
        }
        for (int i = 0; i <= sheet.getLastRowNum(); i++) {
            Row row = sheet.getRow(i);
            if (row == null) {
                continue;
            }
            for (int cellNum = row.getFirstCellNum(); cellNum < row.getLastCellNum(); cellNum++) {
                if (cellNum < 0) {
                    continue;
                }
                Cell cell = row.getCell(cellNum);
                if (cell == null || cell.getCellType() != CellType.STRING) {
                    continue;
                }
                String text = cell.getStringCellValue();
                if (StringUtils.isBlank(text) || !text.contains("{")) {
                    continue;
                }
                String replaced = text;
                for (Map.Entry<String, Object> entry : data.entrySet()) {
                    Object value = entry.getValue();
                    if (value instanceof Collection<?>) {
                        continue;
                    }
                    String key = "{" + entry.getKey() + "}";
                    replaced = replaced.replace(key, StringUtils.defaultString(Objects.toString(value, ""), ""));
                }
                if (!Objects.equals(text, replaced)) {
                    cell.setCellValue(replaced);
                }
            }
        }
    }

    private CellStyle buildContentTextStyle(Workbook workbook, Row templateRow) {
        CellStyle textStyle = workbook.createCellStyle();
        short textFormat = workbook.createDataFormat().getFormat("@");
        if (templateRow != null && templateRow.getCell(1) != null && templateRow.getCell(1).getCellStyle() != null) {
            textStyle.cloneStyleFrom(templateRow.getCell(1).getCellStyle());
        }
        textStyle.setDataFormat(textFormat);
        return textStyle;
    }

    private void copyTemplateStyles(Row templateRow, Row targetRow) {
        if (templateRow == null) {
            return;
        }
        for (int col = 0; col <= 6; col++) {
            Cell templateCell = templateRow.getCell(col);
            if (templateCell == null || templateCell.getCellStyle() == null) {
                continue;
            }
            Cell targetCell = targetRow.getCell(col);
            if (targetCell == null) {
                targetCell = targetRow.createCell(col);
            }
            targetCell.setCellStyle(templateCell.getCellStyle());
        }
    }

    private void setCellAsText(Row row, int index, String value, CellStyle style) {
        Cell cell = row.getCell(index);
        if (cell == null) {
            cell = row.createCell(index);
        }
        cell.setCellStyle(style);
        cell.setCellValue(StringUtils.defaultString(value, ""));
    }

    private void setCellValue(Row row, int index, String value) {
        Cell cell = row.getCell(index);
        if (cell == null) {
            cell = row.createCell(index);
        }
        cell.setCellValue(StringUtils.defaultString(value, ""));
    }

    private void mergeTrainingContentCells(Sheet sheet, int rowIndex) {
        CellRangeAddress merge = new CellRangeAddress(rowIndex, rowIndex, 1, 2);
        if (!isMergedRegionExists(sheet, merge)) {
            sheet.addMergedRegion(merge);
        }
    }

    private boolean isMergedRegionExists(Sheet sheet, CellRangeAddress target) {
        for (int i = 0; i < sheet.getNumMergedRegions(); i++) {
            CellRangeAddress region = sheet.getMergedRegion(i);
            if (region.getFirstRow() == target.getFirstRow()
                    && region.getLastRow() == target.getLastRow()
                    && region.getFirstColumn() == target.getFirstColumn()
                    && region.getLastColumn() == target.getLastColumn()) {
                return true;
            }
        }
        return false;
    }

    private void alignRowCellsLeft(Row row, int startCol, int endCol) {
        for (int col = startCol; col <= endCol; col++) {
            Cell cell = row.getCell(col);
            if (cell == null) {
                continue;
            }
            CellStyle sourceStyle = cell.getCellStyle();
            if (sourceStyle == null) {
                continue;
            }
            Workbook workbook = row.getSheet().getWorkbook();
            CellStyle leftStyle = workbook.createCellStyle();
            leftStyle.cloneStyleFrom(sourceStyle);
            if (col == 0 || col == 5) {
                leftStyle.setAlignment(HorizontalAlignment.CENTER);
            } else {
                leftStyle.setAlignment(HorizontalAlignment.LEFT);
            }
            leftStyle.setVerticalAlignment(VerticalAlignment.CENTER);
            cell.setCellStyle(leftStyle);
        }
    }

    private int findDataStartRow(Sheet sheet) {
        for (int i = 0; i <= sheet.getLastRowNum(); i++) {
            Row row = sheet.getRow(i);
            if (row == null) {
                continue;
            }
            String c0 = getCellText(row, 0);
            String c1 = getCellText(row, 1);
            if ("培训时间".equals(c0) && "培训内容".equals(c1)) {
                return i + 1;
            }
        }
        return -1;
    }

    private String getCellText(Row row, int index) {
        Cell cell = row.getCell(index);
        if (cell == null) {
            return "";
        }
        return switch (cell.getCellType()) {
            case STRING -> cell.getStringCellValue();
            case NUMERIC -> String.valueOf(cell.getNumericCellValue());
            case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
            default -> "";
        };
    }

    private record ExportDriverRecord(HotDriverVo driver, List<PersonalAnnualRecordCourseVo> courses) {
    }
}
