package de.gecheckt.pdf.umbenenner.adapter.out.pdfextraction;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;

import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;

import de.gecheckt.pdf.umbenenner.application.port.out.PdfTextExtractionPort;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionResult;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionSuccess;
import de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError;
import de.gecheckt.pdf.umbenenner.domain.model.PdfPageCount;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;

/**
 * PDFBox-based implementation of {@link PdfTextExtractionPort}.
 * <p>
 * Extracts text content and page count from a single PDF document
 * using Apache PDFBox. All technical problems during extraction are reported as
 * {@link de.gecheckt.pdf.umbenenner.domain.model.PdfExtractionTechnicalError}.
 * <p>
 * Design:
 * <ul>
 *   <li>Uses PDFBox 3.x for PDF processing</li>
 *   <li>Extracts complete text from all pages (may be empty)</li>
 *   <li>Counts total page count</li>
 *   <li>Returns results as typed {@link PdfExtractionResult} (no exceptions thrown)</li>
 *   <li>All extraction failures are treated as technical errors</li>
 *   <li>PDFBox is encapsulated and never exposed beyond this adapter</li>
 * </ul>
 * <p>
 * Success criteria:
 * <ul>
 *   <li>PDF file is loadable by PDFBox</li>
 *   <li>Page count is determinable (>= 1)</li>
 *   <li>Text can be extracted (may be empty string)</li>
 *   <li>All three values are combined into {@link PdfExtractionSuccess}</li>
 * </ul>
 * <p>
 * Technical error cases:
 * <ul>
 *   <li>File not found or unreadable</li>
 *   <li>PDF cannot be loaded by PDFBox (any load error)</li>
 *   <li>Page count cannot be determined</li>
 *   <li>Text extraction fails or throws exception</li>
 * </ul>
 * <p>
 * Out of scope (handled elsewhere):
 * <ul>
 *   <li>Fachliche Bewertung des extrahierten Texts</li>
 *   <li>Page limit checking</li>
 *   <li>Text normalization or preprocessing</li>
 * </ul>
 */
public class PdfTextExtractionPortAdapter implements PdfTextExtractionPort {

    /**
     * Extracts text content and page count from a single PDF document.
     * <p>
     * Reads the file identified by the candidate's locator, uses PDFBox to extract
     * text from all pages, and counts the total page count.
     * <p>
     * The locator is expected to contain an absolute file path as a String (adapter-internal convention).
     * <p>
     * All technical problems are reported as {@link PdfExtractionTechnicalError}.
     * Fachliche Bewertungen like "text is not usable" are handled elsewhere.
     *
     * @param candidate the document to extract; must be non-null
     * @return a {@link PdfExtractionResult} encoding the outcome:
     *         <ul>
     *           <li>Success: PDF loaded, text extracted (may be empty), page count determined</li>
     *           <li>Technical error: any I/O, file access, or PDFBox loading/parsing problem</li>
     *         </ul>
     * @throws NullPointerException if candidate is null
     */
    @Override
    public PdfExtractionResult extractTextAndPageCount(SourceDocumentCandidate candidate) {
        Objects.requireNonNull(candidate, "candidate must not be null");

        String filePath = candidate.locator().value();

        try {
            // Validate file exists and is readable
            var path = Paths.get(filePath);
            if (!Files.exists(path)) {
                return new PdfExtractionTechnicalError(
                    "PDF file not found: " + filePath,
                    null);
            }
            if (!Files.isReadable(path)) {
                return new PdfExtractionTechnicalError(
                    "PDF file is not readable: " + filePath,
                    null);
            }

            // Load and process PDF using PDFBox Loader (3.x API)
            PDDocument document = Loader.loadPDF(path.toFile());
            try {
                int pageCount = document.getNumberOfPages();

                // Handle case of zero pages as technical error
                // (PdfPageCount requires >= 1, so this is a constraint violation)
                if (pageCount < 1) {
                    return new PdfExtractionTechnicalError(
                        "PDF has zero pages, cannot extract content",
                        null);
                }

                // Extract text from all pages
                // Note: extractedText may be empty string, which is valid (no fachliche validation here)
                PDFTextStripper textStripper = new PDFTextStripper();
                String extractedText = textStripper.getText(document);

                // Success: return extracted text and page count
                // (Empty text is not an error; fachliche validation is handled elsewhere)
                PdfPageCount pageCountTyped = new PdfPageCount(pageCount);
                return new PdfExtractionSuccess(extractedText, pageCountTyped);
            } finally {
                document.close();
            }

        } catch (IOException e) {
            // All I/O and PDFBox loading/parsing errors are technical errors
            String errorMessage = e.getMessage() != null ? e.getMessage() : e.toString();
            return new PdfExtractionTechnicalError(
                "Failed to load or parse PDF: " + errorMessage,
                e);
        } catch (Exception e) {
            // Catch-all for unexpected errors
            String errorMessage = e.getMessage() != null ? e.getMessage() : e.toString();
            return new PdfExtractionTechnicalError(
                "Unexpected error during PDF extraction: " + errorMessage,
                e);
        }
    }
}
