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

import java.util.Optional;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

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

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

import stirling.software.SPDF.config.swagger.StandardPdfResponse;
import stirling.software.SPDF.model.json.PdfJsonDocument;
import stirling.software.SPDF.model.json.PdfJsonMetadata;
import stirling.software.SPDF.service.PdfJsonConversionService;
import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.annotations.api.ConvertApi;
import stirling.software.common.model.api.GeneralFile;
import stirling.software.common.model.api.PDFFile;
import stirling.software.common.service.JobOwnershipService;
import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.WebResponseUtils;

@Slf4j
@ConvertApi
@RequiredArgsConstructor
public class ConvertPdfJsonController {

    private final PdfJsonConversionService pdfJsonConversionService;

    @Autowired(required = false)
    private JobOwnershipService jobOwnershipService;

    @AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/text-editor")
    @Operation(
            summary = "Convert PDF to Text Editor Format",
            description =
                    "Extracts PDF text, fonts, and metadata into an editable JSON structure for the text editor tool. Input:PDF Output:JSON Type:SISO")
    public ResponseEntity<byte[]> convertPdfToJson(
            @ModelAttribute PDFFile request,
            @RequestParam(value = "lightweight", defaultValue = "false") boolean lightweight)
            throws Exception {
        MultipartFile inputFile = request.getFileInput();
        if (inputFile == null) {
            throw ExceptionUtils.createNullArgumentException("fileInput");
        }

        byte[] jsonBytes = pdfJsonConversionService.convertPdfToJson(inputFile, lightweight);
        String originalName = inputFile.getOriginalFilename();
        String baseName =
                (originalName != null && !originalName.isBlank())
                        ? Filenames.toSimpleFileName(originalName).replaceFirst("[.][^.]+$", "")
                        : "document";
        String docName = baseName + ".json";
        return WebResponseUtils.bytesToWebResponse(jsonBytes, docName, MediaType.APPLICATION_JSON);
    }

    @AutoJobPostMapping(consumes = "multipart/form-data", value = "/text-editor/pdf")
    @StandardPdfResponse
    @Operation(
            summary = "Convert Text Editor Format to PDF",
            description =
                    "Rebuilds a PDF from the editable JSON structure generated by the text editor tool. Input:JSON Output:PDF Type:SISO")
    public ResponseEntity<byte[]> convertJsonToPdf(@ModelAttribute GeneralFile request)
            throws Exception {
        MultipartFile jsonFile = request.getFileInput();
        if (jsonFile == null) {
            throw ExceptionUtils.createNullArgumentException("fileInput");
        }

        byte[] pdfBytes = pdfJsonConversionService.convertJsonToPdf(jsonFile);
        String originalName = jsonFile.getOriginalFilename();
        String baseName =
                (originalName != null && !originalName.isBlank())
                        ? Filenames.toSimpleFileName(originalName).replaceFirst("[.][^.]+$", "")
                        : "document";
        String docName = baseName.endsWith(".pdf") ? baseName : baseName + ".pdf";
        return WebResponseUtils.bytesToWebResponse(pdfBytes, docName);
    }

    @PostMapping(consumes = "multipart/form-data", value = "/pdf/text-editor/metadata")
    @Operation(
            summary = "Extract PDF metadata for text editor lazy loading",
            description =
                    "Extracts document metadata, fonts, and page dimensions for the text editor tool. Caches the document for"
                            + " subsequent page requests. Returns a server-generated jobId scoped to the"
                            + " authenticated user. Input:PDF Output:JSON Type:SISO")
    public ResponseEntity<byte[]> extractPdfMetadata(@ModelAttribute PDFFile request)
            throws Exception {
        MultipartFile inputFile = request.getFileInput();
        if (inputFile == null) {
            throw ExceptionUtils.createNullArgumentException("fileInput");
        }

        // Generate server-side UUID for job
        String baseJobId = UUID.randomUUID().toString();

        // Scope job to authenticated user if security is enabled
        String scopedJobKey = getScopedJobKey(baseJobId);

        log.info("Extracting metadata for PDF, assigned jobId: {}", scopedJobKey);

        byte[] jsonBytes =
                pdfJsonConversionService.extractDocumentMetadata(inputFile, scopedJobKey);
        String originalName = inputFile.getOriginalFilename();
        String baseName =
                (originalName != null && !originalName.isBlank())
                        ? Filenames.toSimpleFileName(originalName).replaceFirst("[.][^.]+$", "")
                        : "document";
        String docName = baseName + "_metadata.json";

        // Return jobId in response header for client
        return ResponseEntity.ok()
                .header("X-Job-Id", scopedJobKey)
                .contentType(MediaType.APPLICATION_JSON)
                .body(jsonBytes);
    }

    @PostMapping(
            value = "/pdf/text-editor/partial/{jobId}",
            consumes = MediaType.APPLICATION_JSON_VALUE)
    @StandardPdfResponse
    @Operation(
            summary = "Apply incremental edits from text editor to a cached PDF",
            description =
                    "Applies edits for the specified pages of a cached PDF and returns an updated PDF."
                            + " Requires the PDF to have been previously cached via the text editor metadata endpoint."
                            + " The jobId must be obtained from the metadata extraction endpoint.")
    public ResponseEntity<byte[]> exportPartialPdf(
            @PathVariable String jobId,
            @RequestBody PdfJsonDocument document,
            @RequestParam(value = "filename", required = false) String filename)
            throws Exception {
        if (document == null) {
            throw ExceptionUtils.createNullArgumentException("document");
        }

        // Validate job ownership
        validateJobAccess(jobId);

        byte[] pdfBytes = pdfJsonConversionService.exportUpdatedPages(jobId, document);

        String baseName =
                (filename != null && !filename.isBlank())
                        ? Filenames.toSimpleFileName(filename).replaceFirst("[.][^.]+$", "")
                        : Optional.ofNullable(document.getMetadata())
                                .map(PdfJsonMetadata::getTitle)
                                .filter(title -> title != null && !title.isBlank())
                                .orElse("document");
        String docName = baseName.endsWith(".pdf") ? baseName : baseName + ".pdf";
        return WebResponseUtils.bytesToWebResponse(pdfBytes, docName);
    }

    @GetMapping(value = "/pdf/text-editor/page/{jobId}/{pageNumber}")
    @Operation(
            summary = "Extract single page from cached PDF for text editor",
            description =
                    "Retrieves a single page's content from a previously cached PDF document for the text editor tool."
                            + " Requires prior call to /pdf/text-editor/metadata. The jobId must belong to the"
                            + " authenticated user. Output:JSON")
    public ResponseEntity<byte[]> extractSinglePage(
            @PathVariable String jobId, @PathVariable int pageNumber) throws Exception {

        // Validate job ownership
        validateJobAccess(jobId);

        byte[] jsonBytes = pdfJsonConversionService.extractSinglePage(jobId, pageNumber);
        String docName = "page_" + pageNumber + ".json";
        return WebResponseUtils.bytesToWebResponse(jsonBytes, docName, MediaType.APPLICATION_JSON);
    }

    @PostMapping(value = "/pdf/text-editor/clear-cache/{jobId}")
    @Operation(
            summary = "Clear cached PDF document for text editor",
            description =
                    "Manually clears a cached PDF document used by the text editor to free up server resources."
                            + " Called automatically after 30 minutes. The jobId must belong to the"
                            + " authenticated user.")
    public ResponseEntity<Void> clearCache(@PathVariable String jobId) {

        // Validate job ownership
        validateJobAccess(jobId);

        pdfJsonConversionService.clearCachedDocument(jobId);
        return ResponseEntity.ok().build();
    }

    /**
     * Get a scoped job key that includes user ownership when security is enabled.
     *
     * @param baseJobId the base job identifier
     * @return scoped job key, or just baseJobId if no ownership service available
     */
    private String getScopedJobKey(String baseJobId) {
        if (jobOwnershipService != null) {
            return jobOwnershipService.createScopedJobKey(baseJobId);
        }
        // Security disabled, return unsecured job key
        return baseJobId;
    }

    /**
     * Validate that the current user has access to the given job.
     *
     * @param jobId the job identifier to validate
     * @throws SecurityException if current user does not own the job
     */
    private void validateJobAccess(String jobId) {
        if (jobOwnershipService != null) {
            jobOwnershipService.validateJobAccess(jobId);
        }
        // If jobOwnershipService is null (security disabled), allow all access
    }
}
