package de.assecutor.votianlt.service;

import de.assecutor.votianlt.model.invoices.CustomerInvoice;
import de.assecutor.votianlt.model.invoices.InvoiceAuditEntry;
import de.assecutor.votianlt.model.invoices.InvoiceType;
import de.assecutor.votianlt.repository.CustomerInvoiceRepository;
import org.springframework.stereotype.Service;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Export-Funktion für Rechnungen gemäß R-33 und R-34.
 *
 * Bündelt eine Originalrechnung mit allen erzeugten Folgebelegen
 * (Storno, Berichtigung, Ersatzrechnung) sowie einer Manifest-Datei
 * mit Audit-Log und Verkettungsangaben in einer ZIP-Datei.
 */
@Service
public class InvoiceExportService {

    private static final DateTimeFormatter TS_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss",
            Locale.GERMANY);

    private final CustomerInvoiceRepository invoiceRepository;

    public InvoiceExportService(CustomerInvoiceRepository invoiceRepository) {
        this.invoiceRepository = invoiceRepository;
    }

    /**
     * Erzeugt ein ZIP-Archiv mit dem Originalbeleg, allen verlinkten Folgebelegen
     * sowie einer Manifest-Datei. Ist die übergebene Rechnung selbst ein Folgebeleg,
     * wird automatisch das Bündel um die zugehörige Originalrechnung erweitert.
     */
    public byte[] exportInvoicePackage(CustomerInvoice anchor) {
        if (anchor == null) {
            throw new IllegalArgumentException("Rechnung erforderlich.");
        }

        CustomerInvoice root = resolveRoot(anchor);
        List<CustomerInvoice> bundle = collectBundle(root);

        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ZipOutputStream zip = new ZipOutputStream(baos, StandardCharsets.UTF_8)) {

            for (CustomerInvoice invoice : bundle) {
                writePdfEntry(zip, invoice);
            }

            String manifest = buildManifest(root, bundle);
            ZipEntry manifestEntry = new ZipEntry("MANIFEST.txt");
            zip.putNextEntry(manifestEntry);
            zip.write(manifest.getBytes(StandardCharsets.UTF_8));
            zip.closeEntry();

            zip.finish();
            return baos.toByteArray();
        } catch (Exception ex) {
            throw new IllegalStateException("Export fehlgeschlagen: " + ex.getMessage(), ex);
        }
    }

    /**
     * Schlägt einen Dateinamen für das ZIP-Archiv auf Basis des Originalbelegs vor.
     */
    public String suggestFilename(CustomerInvoice anchor) {
        CustomerInvoice root = resolveRoot(anchor);
        String number = root.getInvoiceNumber() != null ? root.getInvoiceNumber() : root.getId();
        return "Rechnung_" + sanitize(number) + ".zip";
    }

    private CustomerInvoice resolveRoot(CustomerInvoice anchor) {
        if (anchor.getType() != InvoiceType.INVOICE && anchor.getOriginalInvoiceId() != null) {
            return invoiceRepository.findById(anchor.getOriginalInvoiceId()).orElse(anchor);
        }
        return anchor;
    }

    private List<CustomerInvoice> collectBundle(CustomerInvoice root) {
        List<CustomerInvoice> result = new ArrayList<>();
        Set<String> seen = new HashSet<>();
        result.add(root);
        seen.add(root.getId());

        for (CustomerInvoice related : invoiceRepository.findByOriginalInvoiceId(root.getId())) {
            if (related.getId() != null && seen.add(related.getId())) {
                result.add(related);
            }
        }
        return result;
    }

    private void writePdfEntry(ZipOutputStream zip, CustomerInvoice invoice) throws java.io.IOException {
        if (invoice.getPdfData() == null || invoice.getPdfData().length == 0) {
            return;
        }
        String label = switch (invoice.getType() != null ? invoice.getType() : InvoiceType.INVOICE) {
        case INVOICE -> "Rechnung";
        case CANCELLATION -> "Storno";
        case CORRECTION -> "Berichtigung";
        };
        String number = invoice.getInvoiceNumber() != null ? invoice.getInvoiceNumber() : invoice.getId();
        String name = sanitize(label + "_" + number) + ".pdf";
        ZipEntry entry = new ZipEntry(name);
        zip.putNextEntry(entry);
        zip.write(invoice.getPdfData());
        zip.closeEntry();
    }

    private String buildManifest(CustomerInvoice root, List<CustomerInvoice> bundle) {
        StringBuilder sb = new StringBuilder();
        sb.append("Rechnungspaket\n");
        sb.append("===============\n\n");
        sb.append("Originalrechnung: ").append(safe(root.getInvoiceNumber()));
        if (root.getInvoiceDate() != null) {
            sb.append(" vom ").append(root.getInvoiceDate());
        }
        sb.append("\n");
        sb.append("Status: ").append(root.getStatus()).append("\n");
        sb.append("Zahlungsstatus: ").append(root.getPaymentStatus()).append("\n");
        if (root.getTotalAmount() != null) {
            sb.append("Gesamtbetrag: ").append(root.getTotalAmount()).append("\n");
        }
        if (root.getPaidAmount() != null) {
            sb.append("Bezahlt: ").append(root.getPaidAmount()).append("\n");
        }

        sb.append("\nEnthaltene Belege:\n");
        for (CustomerInvoice invoice : bundle) {
            sb.append("- [").append(invoice.getType()).append("] ")
                    .append(safe(invoice.getInvoiceNumber()));
            if (invoice.getInvoiceDate() != null) {
                sb.append(" vom ").append(invoice.getInvoiceDate());
            }
            sb.append(" — Status ").append(invoice.getStatus()).append("\n");
        }

        sb.append("\nÄnderungsprotokoll der Originalrechnung:\n");
        List<InvoiceAuditEntry> log = root.getAuditLog();
        if (log == null || log.isEmpty()) {
            sb.append("(keine Einträge)\n");
        } else {
            for (InvoiceAuditEntry entry : log) {
                sb.append("- ");
                sb.append(entry.getTimestamp() != null ? entry.getTimestamp().format(TS_FMT) : "-");
                sb.append(" · ").append(entry.getAction());
                sb.append(" · ").append(safe(entry.getUserDisplayName()));
                if (entry.getReason() != null && !entry.getReason().isBlank()) {
                    sb.append(" — ").append(entry.getReason());
                }
                if (entry.getResultingInvoiceNumber() != null) {
                    sb.append(" → ").append(entry.getResultingInvoiceNumber());
                }
                sb.append("\n");
            }
        }
        sb.append(
                "\nHinweis: Dieses Paket dient der gemeinsamen Aufbewahrung von Original und Folgebelegen.\n");
        sb.append("Die rechtliche Aufbewahrungspflicht liegt beim Aussteller (R-31/R-32).\n");
        return sb.toString();
    }

    private String safe(String value) {
        return value != null ? value : "";
    }

    private String sanitize(String input) {
        if (input == null) {
            return "Beleg";
        }
        return input.replaceAll("[^A-Za-z0-9._-]", "_");
    }
}
