package com.ffii.lioner.modules.lioner.pdf.web;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.ffii.core.response.RecordsRes;
import com.ffii.core.utils.CriteriaArgsBuilder;
import com.ffii.core.utils.Params;
import com.ffii.core.utils.StringUtils;
import com.ffii.lioner.modules.lioner.entity.FileBlob;
import com.ffii.lioner.modules.lioner.pdf.service.PdfMergeService;
import com.ffii.lioner.modules.lioner.pdf.service.PdfService;
import com.ffii.lioner.modules.lioner.service.FileService;
import com.ffii.lioner.modules.lioner.userActionLog.service.UserActionLogService;
import com.itextpdf.io.source.ByteArrayOutputStream;

import jakarta.servlet.http.HttpServletRequest;


@RestController
@RequestMapping("/pdf")
@CrossOrigin(origins = "", allowedHeaders = "")

public class PdfController {
    protected final Log logger = LogFactory.getLog(getClass());

    @Autowired
    private PdfMergeService pdfMergeService;

    @Autowired
    private UserActionLogService userActionLogService;


    @Autowired
    private FileService fileService;

    private PdfService pdfService;

    public PdfController(PdfService pdfService) {
        this.pdfService = pdfService;
    }

    // @GetMapping("/{id}")
	// public List<Map<String, Object>> get(@PathVariable Long id) {
	// 	return Map.of(
	// 			Params.DATA, pdfService.findByClientId(id).orElseThrow(NotFoundException::new)
	// 		);
	// }

    @GetMapping
	public RecordsRes<Map<String, Object>> list(HttpServletRequest request) throws ServletRequestBindingException {
		return new RecordsRes<>(pdfService.list(
				CriteriaArgsBuilder.withRequest(request)
                        .addInteger("clientId")
                        .addInteger("templateId")
                        .addDate("createDateFrom")
                        .addDate("createDateTo")
                        .addStringLike("remarks")
						.build()));
	}

    @GetMapping("/{id}")
	public Map<String, Object> get(@PathVariable Long id) {
        return pdfService.loadPDF(id);
        // return Map.of(
		// 		Params.DATA, pdfService.loadPDF(id)
		// 	);
	}

    // Endpoint to serve the initial template PDF
    // @GetMapping(value = "/template2", produces = MediaType.APPLICATION_PDF_VALUE)
    // public ResponseEntity<byte[]> getPdfTemplate(@RequestParam Long templateId, @RequestParam Long clientId) throws IOException {
    //     byte[] pdfBytes = pdfService.getTemplateForm(templateId, clientId);

    //     HttpHeaders headers = new HttpHeaders();
    //     headers.setContentDispositionFormData("inline", "template_form.pdf");
    //     headers.setContentType(MediaType.APPLICATION_PDF);
    //     return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK);
    // }

    // Endpoint to serve the initial template PDF
    @GetMapping(value = "/template")
    // public ResponseEntity<byte[]> getPdfTemplate(@RequestParam Long templateId, @RequestParam Long clientId) throws IOException {
    public Map<String, Object> getPdfTemplate(@RequestParam Long templateId, @RequestParam Long clientId) throws IOException {
        
        Map<String, Object> pdfTemplate = pdfService.getTemplate(templateId, clientId);
        return pdfTemplate;
    }

    // Get template by template id (Unused now)
    // @GetMapping(value = "/template/{id}", produces = MediaType.APPLICATION_PDF_VALUE)
    // public ResponseEntity<byte[]> getPdfTemplateById(@PathVariable Long id) throws IOException {
        
    //     byte[] pdfBytes = pdfService.getTemplateForm(id, (long) 0);

    //     HttpHeaders headers = new HttpHeaders();
    //     headers.setContentDispositionFormData("inline", "template_form.pdf");
    //     headers.setContentType(MediaType.APPLICATION_PDF);
    //     return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK);
    // }

    // Endpoint to receive the filled PDF from the frontend
    @Transactional(isolation = Isolation.SERIALIZABLE, rollbackFor = Exception.class, readOnly = false)
    @PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    // public Map<String, Object> saveFilledPdf(@RequestParam("pdf") @Valid UpdatePdfReq req, @RequestParam("file") MultipartFile file) {
    // public Map<String, Object> saveFilledPdf(@RequestParam("record") UpdatePdfReq req, @RequestParam("file") MultipartFile file) {
    public Map<String, Object> saveFilledPdf(@RequestParam("record") String recordJson, @RequestParam("file") MultipartFile file) {
        return Map.of(
				Params.DATA,pdfService.saveOrUpdate(recordJson, file)
			);
    }

    // Optional: Endpoint to receive just the form data (e.g., JSON)
    @PostMapping("/saveFormData")
    public ResponseEntity<Map<String, String>> saveFormData(@RequestBody Map<String, Object> formData) {
        System.out.println("Received form data: " + formData);
        Map<String, String> response = new HashMap<>();
        response.put("message", "Form data received and processed successfully.");
        return ResponseEntity.ok(response);
    }

    @GetMapping("/merge-pdf")
    public String mergePdf() {
        try {
            pdfMergeService.mergePdfs();
            return "PDFs merged successfully";
        } catch (IOException e) {
            return "Error merging PDFs: " + e.getMessage();
        }
    }

    @GetMapping(value = "/download-flat/{id}", produces = MediaType.APPLICATION_PDF_VALUE)
    public ResponseEntity<byte[]> getFlatPdfByFilledFormId(@PathVariable Long id) throws IOException {
        logger.info("XXXXXXXXXXXXXXX getFlatPdfByFilledFormId START XXXXXXXXXXXXXX");
        // --- 1. Fetch Data & Set up Variables ---
        Map<String, Object> d = pdfService.getFilledFormPdf(id);
        String templateName = (String) d.get("templateName");
        String lastname = StringUtils.trimToEmpty((String) d.get("lastname"));
        String firstname = StringUtils.trimToEmpty((String) d.get("firstname"));
        lastname = lastname.replaceAll("\\s+", "");
        firstname = firstname.replaceAll("\\s+", "");
        String filename = lastname + firstname + "-" + templateName + ".pdf";

        Long fileId = Optional.ofNullable(d.get("fileId"))
            .map(val -> ((Integer) val).longValue())
            .orElse(null);

        // Safely retrieve byte arrays using Optional chaining (prevents NullPointerExceptions)
        byte[] pdfBytes = Optional.ofNullable(fileId)
                .flatMap(fileService::findFileBlobByFileId) 
                .map(FileBlob::getBytes) 
                .orElse(null);
             
        // --- 2. PDF Merging and Processing ---
        byte[] finalPdfBytes = new byte[0];
        
        logger.info("pdfBytes:" + pdfBytes.length);
        
        finalPdfBytes = pdfMergeService.flatPdfsItext7(pdfBytes);

        // --- 4. Build ResponseEntity ---
        HttpHeaders headers = new HttpHeaders();
        // Use attachment for download, or inline for display in browser
        headers.add("Access-Control-Expose-Headers", "Content-Disposition");
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"");
        headers.setContentType(MediaType.APPLICATION_PDF);
        headers.setContentLength(finalPdfBytes.length);

        //action log
        userActionLogService.createLog("Download flatted Form of Client Code:" + d.get("clientCode") + " Form:" + d.get("templateName") , id, "FlatPDF");

        return new ResponseEntity<>(finalPdfBytes, headers, HttpStatus.OK);
    }

    @GetMapping("/remove-pdf-password")
    public String removePdfPassword() {
        try {
            pdfMergeService.removePdfPassword();
            return "PDFs removePdfPassword successfully";
        } catch (IOException e) {
            return "Error removePdfPassword PDF: " + e.getMessage();
        }
    }

    //this if for upload signiture 1 for PDF form merging later
    @PostMapping("/upload1")
    public ResponseEntity<Map<String, String>> upload1(@RequestParam("file") MultipartFile file, @RequestParam Long refId, @RequestParam String refType) {
        try {
            MultipartFile[] files = new MultipartFile[1];
            files[0] = file;
            
            fileService.saveOrUpdate(files, refId, refType);

            Map<String, String> response = new HashMap<>();
             response.put("message", "File uploaded and processed successfully.");

            Map<String, Object> d = pdfService.getFilledFormPdf(refId);
            //action log
            userActionLogService.createLog("Upload Signiture1 of Client Code:" + d.get("clientCode") + " Form:" + d.get("templateName") , refId, refType);
        
            return ResponseEntity.ok(response);
        } catch (Exception ex) {
            Map<String, String> errorBody = new HashMap<>();
            errorBody.put("message", "File upload failed: " + ex.getMessage()); 
        
            return ResponseEntity
                   .status(HttpStatus.INTERNAL_SERVER_ERROR)
                   .body(errorBody);
        }
    }


    @GetMapping(value = "/download-ff/{id}", produces = MediaType.APPLICATION_PDF_VALUE)
    public ResponseEntity<byte[]> getMergedPdfByFilledFormId(@PathVariable Long id) throws IOException {
        logger.info("XXXXXXXXXXXXXXX getMergedPdfByFilledFormId START XXXXXXXXXXXXXX");
        // --- 1. Fetch Data & Set up Variables ---
        Map<String, Object> d = pdfService.getFilledFormPdf(id);
        String formCode = (String) d.get("formCode");
        String templateName = (String) d.get("templateName");
        String lastname = StringUtils.trimToEmpty((String) d.get("lastname"));
        String firstname = StringUtils.trimToEmpty((String) d.get("firstname"));
        lastname = lastname.replaceAll("\\s+", "");
        firstname = firstname.replaceAll("\\s+", "");
        lastname = lastname.replaceAll("[^a-zA-Z0-9]", "");
        firstname = firstname.replaceAll("[^a-zA-Z0-9]", "");
        String filename = lastname + firstname + "-" + templateName + ".pdf";

        Long fileId = Optional.ofNullable(d.get("fileId"))
            .map(val -> ((Integer) val).longValue())
            .orElse(null);

        Long upload1FileId = Optional.ofNullable(d.get("upload1FileId"))
            .map(val -> ((Integer) val).longValue())
            .orElse(null);

        Long upload2FileId = Optional.ofNullable(d.get("upload2FileId"))
            .map(val -> ((Integer) val).longValue())
            .orElse(null);

        // Safely retrieve byte arrays using Optional chaining (prevents NullPointerExceptions)
        byte[] pdfBytes = Optional.ofNullable(fileId)
                .flatMap(fileService::findFileBlobByFileId) 
                .map(FileBlob::getBytes) 
                .orElse(null);

        // Only fetch upload1 if needed (or if it's generally required)
        byte[] pdfUpload1Bytes = Optional.ofNullable(upload1FileId)
                .flatMap(fileService::findFileBlobByFileId)
                .map(FileBlob::getBytes)
                .orElse(null);

        byte[] pdfUpload2Bytes = Optional.ofNullable(upload2FileId)
                .flatMap(fileService::findFileBlobByFileId)
                .map(FileBlob::getBytes)
                .orElse(null);

        // Check if the primary file is missing
        if (pdfBytes == null || pdfBytes.length == 0) {
            logger.info("Both null:");
            return ResponseEntity.notFound().build();
        }

        // --- 2. PDF Merging and Processing ---
        byte[] finalPdfBytes = new byte[0];
        
        logger.info("pdfBytes:" + pdfBytes.length);
        logger.info("pdfUpload1Bytes:" + pdfUpload1Bytes.length);
        logger.info("formCode:" + formCode);

        finalPdfBytes = pdfMergeService.mergePdfsItext7(formCode, pdfBytes, pdfUpload1Bytes);

        //Forms that need to have 2nd upload sig
        if(d.get("upload2FileId") != null)
            finalPdfBytes = pdfMergeService.mergePdf2sItext7(formCode, finalPdfBytes, pdfUpload2Bytes);
        
        // --- 4. Build ResponseEntity ---
        HttpHeaders headers = new HttpHeaders();
        // Use attachment for download, or inline for display in browser
        headers.add("Access-Control-Expose-Headers", "Content-Disposition");
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"");
        headers.setContentType(MediaType.APPLICATION_PDF);
        headers.setContentLength(finalPdfBytes.length);

        //action log
        userActionLogService.createLog("Download Merged Form of Client Code:" + d.get("clientCode") + " Form:" + d.get("templateName") , id, "MergePDF");

        return new ResponseEntity<>(finalPdfBytes, headers, HttpStatus.OK);
    }

    /*

    @GetMapping(value = "/download-ff", produces = MediaType.APPLICATION_PDF_VALUE)
    public ResponseEntity<byte[]> getMergedPdfByFilledFormId(@PathVariable Long id) throws IOException {
        Map<String, Object> d = pdfService.getFilledFormPdf(id);
        
        //get the filled form id
        ;
        byte[] pdfBytes = fileService.findFileBlobByFileId((Long)d.get("fileId")).orElse(null).getBytes();
        byte[] pdfUpload1Bytes = fileService.findFileBlobByFileId((Long)d.get("upload1FileId")).orElse(null).getBytes();
        PDDocument outputDoc = new PDDocument();

        logger.info("formCode:" + d.get("formCode"));
        if("IDA".equals(d.get("formCode"))){
            //page 9 is the sig page, so remove page 9 of pdf and merge the upload1
            //PDDocument sourceDoc = Loader.loadPDF(pdfBytes);
            try (PDDocument sourceDoc = Loader.loadPDF(pdfBytes)) { 
                int pageCount = sourceDoc.getNumberOfPages();

                logger.info("pageCount:" + pageCount);
            } catch (IOException e) { 
                // This ONLY catches standard IO exceptions. It might not catch the S.O.E.
                logger.info("IOException:" + e.toString());
            }

            try (PDDocument upload1Doc = Loader.loadPDF(pdfUpload1Bytes)) { 
                // ...
            } catch (IOException e) { 
                // This ONLY catches standard IO exceptions. It might not catch the S.O.E.
                logger.info("IOException:" + e.toString());
            }
                
            // You can now work with the document, e.g., get the number of pages
            
            
            for (int i = 0; i < sourceDoc.getNumberOfPages(); i++) {
                PDPage page = sourceDoc.getPage(i);
                outputDoc.addPage(page);
            }

            for (int i = 0; i < upload1Doc.getNumberOfPages(); i++) {
                PDPage page = upload1Doc.getPage(i);
                outputDoc.addPage(page);
            }
            
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentDispositionFormData("inline", "form.pdf");
        headers.setContentType(MediaType.APPLICATION_PDF);
        return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK);
    }*/

    public byte[] convertPdfDocumentToBytes(PDDocument doc) throws IOException {
        
        // 1. Create a stream to hold the PDF's bytes in memory
        // Use try-with-resources to ensure the stream is automatically closed.
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            
            // 2. Save the PDDocument content into the ByteArrayOutputStream
            // This process writes the entire PDF structure (objects, XRef, etc.)
            doc.save(baos);
            
            // 3. Convert the content of the stream to a byte array
            return baos.toByteArray();
            
        } finally {
            // 4. Important: Close the PDDocument to release resources
            if (doc != null) {
                doc.close();
            }
        }
    }

    
    

}
