package de.assecutor.pdftool.zugferd;

import de.assecutor.pdftool.invoice.InvoiceMetadata;
import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType0Font;
import org.junit.jupiter.api.Test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class ZugferdServiceTest {

    private final ZugferdService service = new ZugferdService(new ZugferdValidationService());

    @Test
    void createsZipWithZugferdPdfAndFacturXml() throws Exception {
        byte[] sourcePdf = createSamplePdf();

        ZugferdResult result = service.createZugferdZip(sourcePdf, sampleMetadata());

        assertEquals("RE-2026-0815.zip", result.zipFileName());
        assertTrue(result.valid(), "Mustang-Validierung fehlgeschlagen:\n" + result.validationReport());

        Map<String, byte[]> entries = readZip(result.zip());
        assertEquals(3, entries.size());
        byte[] pdf = entries.get("RE-2026-0815-zugferd.pdf");
        byte[] xml = entries.get("factur-x.xml");
        byte[] report = entries.get("validation-report.xml");
        assertNotNull(pdf, "ZUGFeRD-PDF fehlt im ZIP");
        assertNotNull(xml, "Factur-X-XML fehlt im ZIP");
        assertNotNull(report, "Prüfbericht fehlt im ZIP");

        String xmlText = new String(xml, StandardCharsets.UTF_8);
        assertTrue(xmlText.contains("RE-2026-0815"), "Rechnungsnummer fehlt im XML");
        assertTrue(xmlText.contains("CrossIndustryInvoice"), "Kein CII-XML");
        assertTrue(xmlText.contains("urn:cen.eu:en16931:2017"), "EN16931-Profil fehlt");

        // Das erzeugte PDF muss das XML als eingebettete Datei enthalten
        try (PDDocument doc = Loader.loadPDF(pdf)) {
            String names = doc.getDocumentCatalog().getNames().getEmbeddedFiles().getNames().keySet().toString();
            assertTrue(names.contains("factur-x.xml"), "factur-x.xml nicht ins PDF eingebettet: " + names);
        }
    }

    @Test
    void convertsPdfWhosePageHasNoResources() throws Exception {
        // Mustang 2.17 stürzt ohne Resources-Dictionary mit einer NPE ab —
        // der Service muss solche PDFs vorab reparieren.
        byte[] sourcePdf;
        try (PDDocument doc = new PDDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            doc.addPage(new PDPage());
            doc.save(out);
            sourcePdf = out.toByteArray();
        }

        ZugferdResult result = service.createZugferdZip(sourcePdf, sampleMetadata());

        assertTrue(result.valid(), "Mustang-Validierung fehlgeschlagen:\n" + result.validationReport());
    }

    @Test
    void rejectsNonPdfInput() {
        assertThrows(ZugferdConversionException.class,
                () -> service.createZugferdZip("kein pdf".getBytes(StandardCharsets.UTF_8), sampleMetadata()));
    }

    private static InvoiceMetadata sampleMetadata() {
        return new InvoiceMetadata(
                "RE-2026-0815",
                LocalDate.of(2026, 7, 8),
                LocalDate.of(2026, 7, 1),
                LocalDate.of(2026, 7, 22),
                "EUR",
                "Zahlbar innerhalb von 14 Tagen ohne Abzug.",
                "KR-2026-042",
                "DE75512108001245126199",
                null,
                new InvoiceMetadata.Party("Assecutor Data Service GmbH", "Gerhart-Hauptmann-Weg 14",
                        "21502", "Geesthacht", "DE", "DE261094748", "rechnung@example.com",
                        "+49 40 18 123 771 0"),
                new InvoiceMetadata.Party("Kunde AG", "Beispielweg 2", "10115", "Berlin", "DE", null,
                        "einkauf@example.com", null),
                List.of(new InvoiceMetadata.LineItem("Beratungsleistung", BigDecimal.ONE,
                        new BigDecimal("1500.00"), new BigDecimal("19")))
        );
    }

    private static byte[] createSamplePdf() throws Exception {
        try (PDDocument doc = new PDDocument(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            PDPage page = new PDPage();
            doc.addPage(page);
            try (PDPageContentStream cs = new PDPageContentStream(doc, page)) {
                // PDF/A verlangt eingebettete Schriften; LiberationSans liegt pdfbox bei
                PDType0Font font = PDType0Font.load(doc, PDDocument.class.getResourceAsStream(
                        "/org/apache/pdfbox/resources/ttf/LiberationSans-Regular.ttf"), true);
                cs.beginText();
                cs.setFont(font, 12);
                cs.newLineAtOffset(50, 700);
                cs.showText("Rechnung RE-2026-0815");
                cs.endText();
            }
            doc.save(out);
            return out.toByteArray();
        }
    }

    private static Map<String, byte[]> readZip(byte[] zip) throws Exception {
        Map<String, byte[]> entries = new HashMap<>();
        try (ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream(zip))) {
            ZipEntry entry;
            while ((entry = zis.getNextEntry()) != null) {
                entries.put(entry.getName(), zis.readAllBytes());
            }
        }
        return entries;
    }
}
