package cn.nianxx.thhotel.workflows.reservation.service.impl;

import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationInvoiceChargeRequest;
import cn.nianxx.thhotel.workflows.reservation.common.request.ReservationInvoiceManualGenerationRequest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.PrintSetup;
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.util.CellReference;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

/**
 * Proforma Invoice Excel 模板渲染器。只负责把业务字段写入模板，不负责 PDF 转换和 OSS 上传。
 */
@Component
public class ReservationInvoiceExcelTemplateRenderer {

    private static final String TEMPLATE_PATH = "templates/reservation-invoice/proforma-invoice-v1.xlsx";
    private static final DateTimeFormatter DISPLAY_DATE_FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy");
    private static final String SHEET_NAME = "Proforma Invoice";
    private static final int CHARGE_START_ROW_INDEX = 25;
    private static final int DEFAULT_CHARGE_ROW_COUNT = 10;
    private static final int SUBTOTAL_ROW_INDEX = 35;
    private static final int VAT_ROW_INDEX = 36;
    private static final int TOTAL_ROW_INDEX = 37;
    private static final int PRINT_LAST_ROW_INDEX = 51;
    private static final int PRINT_LAST_COLUMN_INDEX = 7;
    private static final short PRINT_SCALE = 55;

    /**
     * 使用受控 Excel 模板渲染 Invoice，并返回 xlsx 字节。
     */
    public byte[] render(ReservationInvoiceManualGenerationRequest request) {
        try (Workbook workbook = loadTemplateWorkbook(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            Sheet sheet = workbook.getSheetAt(0);
            fillHeader(sheet, request);
            fillChargeRows(sheet, request.invoicePayload().charges());
            fillTotalFormulas(sheet);
            applyPrintSettings(workbook, sheet);
            workbook.setForceFormulaRecalculation(true);
            workbook.write(outputStream);
            return outputStream.toByteArray();
        } catch (IOException exception) {
            throw new ReservationInvoiceGenerationException(
                    org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR,
                    "RESERVATION_INVOICE_TEMPLATE_RENDER_FAILED",
                    "Invoice 模板渲染失败。");
        }
    }

    /**
     * 从 classpath 加载受控模板，避免运行时读取用户上传模板。
     */
    private Workbook loadTemplateWorkbook() throws IOException {
        return new XSSFWorkbook(new ClassPathResource(TEMPLATE_PATH).getInputStream());
    }

    /**
     * 填充 Invoice 抬头、收件人和预订摘要字段。
     */
    private void fillHeader(Sheet sheet, ReservationInvoiceManualGenerationRequest request) {
        var payload = request.invoicePayload();
        var document = payload.document();
        var recipient = payload.recipient();
        var booking = payload.booking();
        var firstCharge = payload.charges().get(0);

        setText(sheet, "H5", "Date : " + DISPLAY_DATE_FORMATTER.format(document.invoiceDate()));
        setText(sheet, "C6", recipient.attention());
        setText(sheet, "C7", recipient.company());
        fillAddress(sheet, recipient.address());
        setText(sheet, "C12", recipient.telephone());
        setText(sheet, "C13", recipient.email());
        setText(sheet, "C14", DISPLAY_DATE_FORMATTER.format(document.bookingDate()));
        setText(sheet, "H19", "Due Date               : " + DISPLAY_DATE_FORMATTER.format(document.dueDate()));
        setText(sheet, "D20", booking.groupName());
        setText(sheet, "D22", DISPLAY_DATE_FORMATTER.format(booking.arrivalDate()));
        setText(sheet, "D23", DISPLAY_DATE_FORMATTER.format(booking.departureDate()));
        setText(sheet, "H20", "Room Rate            : " + formatDecimal(firstCharge.rate())
                + " /rm/n" + roomRateNoteSuffix(booking.roomRateNote()));
        setText(sheet, "H21", "Extra bed              : " + formatDecimal(booking.extraBedRate()));
        setText(sheet, "H22", "No.of room (s)       : " + formatDecimal(totalQuantity(payload.charges())));
        setText(sheet, "H23", "No.of Night (s)       : " + formatDecimal(firstCharge.nights()) + " NIGHTS");
    }

    /**
     * 填充费用明细。第一版使用模板默认 10 行，超过行数由 Service 层提前拦截。
     */
    private void fillChargeRows(Sheet sheet, List<ReservationInvoiceChargeRequest> charges) {
        clearChargeRows(sheet);
        for (int index = 0; index < charges.size(); index++) {
            ReservationInvoiceChargeRequest charge = charges.get(index);
            int rowIndex = CHARGE_START_ROW_INDEX + index;
            Row row = row(sheet, rowIndex);
            setCellText(row, 1, charge.description());
            setCellText(row, 3, charge.roomType());
            setCellNumber(row, 4, charge.quantity());
            setCellNumber(row, 5, charge.rate());
            setCellNumber(row, 6, charge.nights());
            setCellFormula(row, 7, "E" + (rowIndex + 1) + "*F" + (rowIndex + 1) + "*G" + (rowIndex + 1));
        }
    }

    /**
     * 写入模板汇总公式，保持 PDF 转换前由 LibreOffice 重新计算。
     */
    private void fillTotalFormulas(Sheet sheet) {
        Row subtotalRow = row(sheet, SUBTOTAL_ROW_INDEX);
        Row vatRow = row(sheet, VAT_ROW_INDEX);
        Row totalRow = row(sheet, TOTAL_ROW_INDEX);
        setCellFormula(subtotalRow, 7, "SUM(H26:H35)/1.07");
        setCellFormula(vatRow, 7, "SUM(H26:H35)-H36");
        setCellFormula(totalRow, 7, "SUM(H26:H35)");
    }

    /**
     * 固定 PDF 输出版式，避免模板被编辑后再次变成多页或 Letter 纸张。
     */
    private void applyPrintSettings(Workbook workbook, Sheet sheet) {
        int sheetIndex = workbook.getSheetIndex(sheet);
        workbook.setSheetName(sheetIndex, SHEET_NAME);
        workbook.setPrintArea(sheetIndex, 0, PRINT_LAST_COLUMN_INDEX, 0, PRINT_LAST_ROW_INDEX);
        PrintSetup printSetup = sheet.getPrintSetup();
        printSetup.setPaperSize(PrintSetup.A4_PAPERSIZE);
        printSetup.setLandscape(false);
        printSetup.setScale(PRINT_SCALE);
        sheet.setAutobreaks(false);
    }

    /**
     * 清理模板默认明细行，避免历史模板残留值进入输出文件。
     */
    private void clearChargeRows(Sheet sheet) {
        for (int index = 0; index < DEFAULT_CHARGE_ROW_COUNT; index++) {
            Row row = row(sheet, CHARGE_START_ROW_INDEX + index);
            for (int cellIndex = 1; cellIndex <= 7; cellIndex++) {
                Cell cell = cell(row, cellIndex);
                cell.setBlank();
            }
        }
    }

    /**
     * 地址字段最多拆成三行展示，兼顾模板空间和用户手填长地址。
     */
    private void fillAddress(Sheet sheet, String address) {
        List<String> addressLines = splitAddressLines(address);
        setText(sheet, "C8", addressLines.get(0));
        setText(sheet, "C9", addressLines.get(1));
        setText(sheet, "C10", addressLines.get(2));
    }

    /**
     * 优先按换行拆分；无换行时按逗号做温和换行，避免单行地址在 PDF 中过长。
     */
    private List<String> splitAddressLines(String address) {
        if (address == null || address.isBlank()) {
            return List.of("", "", "");
        }
        String normalized = address.trim();
        String[] hardLines = normalized.split("\\R");
        if (hardLines.length > 1) {
            return firstThreeLines(List.of(hardLines));
        }
        List<String> lines = new ArrayList<>();
        StringBuilder current = new StringBuilder();
        for (String part : normalized.split(",")) {
            String segment = part.trim();
            if (segment.isEmpty()) {
                continue;
            }
            String candidate = current.length() == 0 ? segment : current + ", " + segment;
            if (candidate.length() > 58 && lines.size() < 2 && current.length() > 0) {
                lines.add(current.toString());
                current = new StringBuilder(segment);
            } else {
                current = new StringBuilder(candidate);
            }
        }
        if (current.length() > 0) {
            lines.add(current.toString());
        }
        return firstThreeLines(lines);
    }

    private List<String> firstThreeLines(List<String> lines) {
        List<String> result = new ArrayList<>(3);
        for (int index = 0; index < lines.size(); index++) {
            String line = lines.get(index);
            if (result.size() == 2) {
                String remaining = String.join(", ", lines.subList(index, lines.size()));
                result.add(remaining);
                break;
            }
            result.add(line == null ? "" : line.trim());
        }
        while (result.size() < 3) {
            result.add("");
        }
        return result;
    }

    private Row row(Sheet sheet, int rowIndex) {
        Row row = sheet.getRow(rowIndex);
        return row == null ? sheet.createRow(rowIndex) : row;
    }

    private Cell cell(Row row, int cellIndex) {
        Cell cell = row.getCell(cellIndex);
        return cell == null ? row.createCell(cellIndex) : cell;
    }

    private void setText(Sheet sheet, String cellRef, String value) {
        CellReference reference = new CellReference(cellRef);
        setCellText(row(sheet, reference.getRow()), reference.getCol(), value);
    }

    private void setCellText(Row row, int cellIndex, String value) {
        cell(row, cellIndex).setCellValue(value == null ? "" : value);
    }

    private void setCellNumber(Row row, int cellIndex, BigDecimal value) {
        cell(row, cellIndex).setCellValue(value == null ? 0D : value.doubleValue());
    }

    private void setCellFormula(Row row, int cellIndex, String formula) {
        cell(row, cellIndex).setCellFormula(formula);
    }

    private BigDecimal totalQuantity(List<ReservationInvoiceChargeRequest> charges) {
        return charges.stream()
                .map(ReservationInvoiceChargeRequest::quantity)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    private String roomRateNoteSuffix(String note) {
        if (note == null || note.isBlank()) {
            return "";
        }
        return " (" + note.trim() + ")";
    }

    private String formatDecimal(BigDecimal value) {
        if (value == null) {
            return "";
        }
        return value.stripTrailingZeros().toPlainString();
    }
}
