package stirling.software.SPDF.controller.api.misc;

import java.awt.Color;
import java.io.IOException;
import java.util.List;
import java.util.Locale;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.font.Standard14Fonts;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile;

import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation;

import lombok.RequiredArgsConstructor;

import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.api.misc.AddPageNumbersRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.MiscApi;
import stirling.software.common.enumeration.ResourceWeight;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.TempFile;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;

@MiscApi
@RequiredArgsConstructor
public class PageNumbersController {

    private final CustomPDFDocumentFactory pdfDocumentFactory;
    private final TempFileManager tempFileManager;

    @AutoJobPostMapping(
            value = "/add-page-numbers",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            resourceWeight = ResourceWeight.SMALL_WEIGHT)
    @StandardPdfResponse
    @Operation(
            summary = "Add page numbers to a PDF document",
            description =
                    "This operation takes an input PDF file and adds page numbers to it. Input:PDF"
                            + " Output:PDF Type:SISO")
    public ResponseEntity<Resource> addPageNumbers(@ModelAttribute AddPageNumbersRequest request)
            throws IOException {

        MultipartFile file = request.getFileInput();
        String customMargin = request.getCustomMargin();
        int position = request.getPosition();
        int pageNumber = request.getStartingNumber();
        String pagesToNumber = request.getPagesToNumber();
        String customText = request.getCustomText();
        int zeroPad = request.getZeroPad();
        float fontSize = request.getFontSize();
        String fontType = request.getFontType();
        String fontColor = request.getFontColor();
        // compute padded number string where requested
        String formatN;
        if (zeroPad > 0) {
            formatN = String.format("%%0%dd", Math.max(0, zeroPad));
        } else {
            formatN = "%d";
        }

        Color color = Color.BLACK;
        if (fontColor != null && !fontColor.trim().isEmpty()) {
            try {
                color = Color.decode(fontColor);
            } catch (NumberFormatException e) {
                color = Color.BLACK;
            }
        }

        try (PDDocument document = pdfDocumentFactory.load(file)) {
            float marginFactor =
                    switch (customMargin == null ? "" : customMargin.toLowerCase(Locale.ROOT)) {
                        case "small" -> 0.02f;
                        case "large" -> 0.05f;
                        case "x-large" -> 0.075f;
                        case "medium" -> 0.035f;
                        default -> 0.035f;
                    };

            if (pagesToNumber == null || pagesToNumber.isEmpty()) {
                pagesToNumber = "all";
            }
            if (customText == null || customText.isEmpty()) {
                customText = "{n}";
            }

            List<Integer> pagesToNumberList =
                    GeneralUtils.parsePageList(
                            pagesToNumber.split(","), document.getNumberOfPages());

            // Clamp position to 1..9 (1 = top-left, 9 = bottom-right)
            int pos = Math.max(1, Math.min(9, position));

            for (int i : pagesToNumberList) {
                PDPage page = document.getPage(i);
                PDRectangle pageSize = page.getMediaBox();

                String nFormatted = String.format(formatN, pageNumber);
                String text =
                        customText
                                .replace("{n}", nFormatted)
                                .replace("{total}", String.valueOf(document.getNumberOfPages()))
                                .replace(
                                        "{filename}",
                                        GeneralUtils.removeExtension(
                                                Filenames.toSimpleFileName(
                                                        file.getOriginalFilename())));

                PDType1Font currentFont =
                        switch (fontType == null ? "" : fontType.toLowerCase(Locale.ROOT)) {
                            case "courier" -> new PDType1Font(Standard14Fonts.FontName.COURIER);
                            case "times" -> new PDType1Font(Standard14Fonts.FontName.TIMES_ROMAN);
                            default -> new PDType1Font(Standard14Fonts.FontName.HELVETICA);
                        };

                // Text dimensions and font metrics
                float textWidth = currentFont.getStringWidth(text) / 1000f * fontSize;
                float ascent = currentFont.getFontDescriptor().getAscent() / 1000f * fontSize;
                float descent = currentFont.getFontDescriptor().getDescent() / 1000f * fontSize;

                // Derive column/row in range 1..3 (1 = left/top, 2 = center/middle, 3 =
                // right/bottom)
                int col = ((pos - 1) % 3) + 1; // 1 = left, 2 = center, 3 = right
                int row = ((pos - 1) / 3) + 1; // 1 = top, 2 = middle, 3 = bottom

                // Anchor coordinates with margin
                float leftX = pageSize.getLowerLeftX() + marginFactor * pageSize.getWidth();
                float midX = pageSize.getLowerLeftX() + pageSize.getWidth() / 2f;
                float rightX = pageSize.getUpperRightX() - marginFactor * pageSize.getWidth();

                float botY = pageSize.getLowerLeftY() + marginFactor * pageSize.getHeight();
                float midY = pageSize.getLowerLeftY() + pageSize.getHeight() / 2f;
                float topY = pageSize.getUpperRightY() - marginFactor * pageSize.getHeight();

                // Horizontal alignment: left = anchor, center = centered, right = right-aligned
                float x =
                        switch (col) {
                            case 1 -> leftX;
                            case 2 -> midX - textWidth / 2f;
                            default -> rightX - textWidth;
                        };

                // Vertical alignment (baseline!):
                // top    = align text top at topY,
                // middle = optical middle using ascent/descent,
                // bottom = baseline at botY
                float y =
                        switch (row) {
                            case 1 -> topY - ascent;
                            case 2 -> midY - (ascent + descent) / 2f;
                            default -> botY;
                        };

                try (PDPageContentStream contentStream =
                        new PDPageContentStream(
                                document,
                                page,
                                PDPageContentStream.AppendMode.APPEND,
                                true,
                                true)) {
                    contentStream.beginText();
                    contentStream.setFont(currentFont, fontSize);
                    contentStream.setNonStrokingColor(color);
                    contentStream.newLineAtOffset(x, y);
                    contentStream.showText(text);
                    contentStream.endText();
                }

                pageNumber++;
            }

            TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
            try {
                document.save(tempOut.getFile());
            } catch (IOException e) {
                tempOut.close();
                throw e;
            }

            return WebResponseUtils.pdfFileToWebResponse(
                    tempOut,
                    GeneralUtils.generateFilename(
                            file.getOriginalFilename(), "_page_numbers_added.pdf"));
        }
    }
}
