package stirling.software.SPDF.controller.api;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDDocumentOutline;
import org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline.PDOutlineItem;
import org.springframework.core.io.Resource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import io.swagger.v3.oas.annotations.Operation;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import stirling.software.SPDF.model.api.EditTableOfContentsRequest;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.GeneralApi;
import stirling.software.common.enumeration.ResourceWeight;
import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.TempFileManager;
import stirling.software.common.util.WebResponseUtils;

import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.ObjectMapper;

@GeneralApi
@Slf4j
@RequiredArgsConstructor
public class EditTableOfContentsController {

    private final CustomPDFDocumentFactory pdfDocumentFactory;
    private final ObjectMapper objectMapper;
    private final TempFileManager tempFileManager;

    @AutoJobPostMapping(
            value = "/extract-bookmarks",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            resourceWeight = ResourceWeight.SMALL_WEIGHT)
    @Operation(
            summary = "Extract PDF Bookmarks",
            description = "Extracts bookmarks/table of contents from a PDF document as JSON.")
    public ResponseEntity<List<Map<String, Object>>> extractBookmarks(
            @RequestParam("file") MultipartFile file) throws Exception {
        try (PDDocument document = pdfDocumentFactory.load(file)) {
            PDDocumentOutline outline = document.getDocumentCatalog().getDocumentOutline();

            if (outline == null) {
                log.info("No outline/bookmarks found in PDF");
                return ResponseEntity.ok(new ArrayList<>());
            }

            List<Map<String, Object>> bookmarks = extractBookmarkItems(document, outline);
            return ResponseEntity.ok(bookmarks);
        }
    }

    private List<Map<String, Object>> extractBookmarkItems(
            PDDocument document, PDDocumentOutline outline) throws Exception {
        List<Map<String, Object>> bookmarks = new ArrayList<>();
        PDOutlineItem current = outline.getFirstChild();

        while (current != null) {
            Map<String, Object> bookmark = new HashMap<>();

            // Get bookmark title
            String title = current.getTitle();
            bookmark.put("title", title);

            // Get page number (1-based for UI purposes)
            PDPage page = current.findDestinationPage(document);
            if (page != null) {
                int pageIndex = document.getPages().indexOf(page);
                bookmark.put("pageNumber", pageIndex + 1);
            } else {
                bookmark.put("pageNumber", 1);
            }

            // Process children if any
            PDOutlineItem child = current.getFirstChild();
            if (child != null) {
                List<Map<String, Object>> children = new ArrayList<>();

                while (child != null) {
                    // Recursively process child items
                    Map<String, Object> childBookmark = processChild(document, child);
                    children.add(childBookmark);
                    child = child.getNextSibling();
                }

                bookmark.put("children", children);
            } else {
                bookmark.put("children", new ArrayList<>());
            }

            bookmarks.add(bookmark);
            current = current.getNextSibling();
        }

        return bookmarks;
    }

    private Map<String, Object> processChild(PDDocument document, PDOutlineItem item)
            throws Exception {
        Map<String, Object> bookmark = new HashMap<>();

        // Get bookmark title
        String title = item.getTitle();
        bookmark.put("title", title);

        // Get page number (1-based for UI purposes)
        PDPage page = item.findDestinationPage(document);
        if (page != null) {
            int pageIndex = document.getPages().indexOf(page);
            bookmark.put("pageNumber", pageIndex + 1);
        } else {
            bookmark.put("pageNumber", 1);
        }

        // Process children if any
        PDOutlineItem child = item.getFirstChild();
        if (child != null) {
            List<Map<String, Object>> children = new ArrayList<>();

            while (child != null) {
                // Recursively process child items
                Map<String, Object> childBookmark = processChild(document, child);
                children.add(childBookmark);
                child = child.getNextSibling();
            }

            bookmark.put("children", children);
        } else {
            bookmark.put("children", new ArrayList<>());
        }

        return bookmark;
    }

    @AutoJobPostMapping(
            value = "/edit-table-of-contents",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            resourceWeight = ResourceWeight.SMALL_WEIGHT)
    @Operation(
            summary = "Edit Table of Contents",
            description = "Add or edit bookmarks/table of contents in a PDF document.")
    public ResponseEntity<Resource> editTableOfContents(
            @ModelAttribute EditTableOfContentsRequest request) throws Exception {
        MultipartFile file = request.getFileInput();

        try (PDDocument document = pdfDocumentFactory.load(file)) {

            // Parse the bookmark data from JSON
            List<BookmarkItem> bookmarks =
                    objectMapper.readValue(
                            request.getBookmarkData(), new TypeReference<List<BookmarkItem>>() {});

            // Create a new document outline
            PDDocumentOutline outline = new PDDocumentOutline();
            document.getDocumentCatalog().setDocumentOutline(outline);

            // Add bookmarks to the outline
            addBookmarksToOutline(document, outline, bookmarks);

            return WebResponseUtils.pdfDocToWebResponse(
                    document,
                    GeneralUtils.generateFilename(file.getOriginalFilename(), "_with_toc.pdf"),
                    tempFileManager);
        }
    }

    private void addBookmarksToOutline(
            PDDocument document, PDDocumentOutline outline, List<BookmarkItem> bookmarks) {
        for (BookmarkItem bookmark : bookmarks) {
            PDOutlineItem item = createOutlineItem(document, bookmark);
            outline.addLast(item);

            if (bookmark.getChildren() != null && !bookmark.getChildren().isEmpty()) {
                addChildBookmarks(document, item, bookmark.getChildren());
            }
        }
    }

    private void addChildBookmarks(
            PDDocument document, PDOutlineItem parent, List<BookmarkItem> children) {
        for (BookmarkItem child : children) {
            PDOutlineItem item = createOutlineItem(document, child);
            parent.addLast(item);

            if (child.getChildren() != null && !child.getChildren().isEmpty()) {
                addChildBookmarks(document, item, child.getChildren());
            }
        }
    }

    private PDOutlineItem createOutlineItem(PDDocument document, BookmarkItem bookmark) {
        PDOutlineItem item = new PDOutlineItem();
        item.setTitle(bookmark.getTitle());

        // Get the target page - adjust for 0-indexed pages in PDFBox
        int pageIndex = bookmark.getPageNumber() - 1;
        if (pageIndex < 0) {
            pageIndex = 0;
        } else if (pageIndex >= document.getNumberOfPages()) {
            pageIndex = document.getNumberOfPages() - 1;
        }

        PDPage page = document.getPage(pageIndex);
        item.setDestination(page);

        return item;
    }

    // Inner class to represent bookmarks in JSON
    @Setter
    @Getter
    public static class BookmarkItem {
        private String title;
        private int pageNumber;
        private List<BookmarkItem> children = new ArrayList<>();
    }
}
