package de.assecutor.votianlt.service;

import de.assecutor.votianlt.model.invoices.CustomerInvoice;
import de.assecutor.votianlt.model.invoices.CustomerInvoiceItem;
import de.assecutor.votianlt.model.invoices.InvoiceType;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;

/**
 * Prüft eine {@link CustomerInvoice} auf die Pflichtangaben nach § 14 UStG
 * sowie auf interne Konsistenz (Beträge, Items). Wird vor jeder Festschreibung
 * (Übergang DRAFT → ISSUED) aufgerufen; eine festgeschriebene Rechnung darf
 * keine Pflichtfeld-Lücken mehr haben, da sie nach R-08 nicht mehr direkt
 * änderbar ist.
 *
 * Nicht abgedeckt (bewusst):
 * <ul>
 *   <li>Lückenlose Rechnungsnummer-Vergabe (separater Block).</li>
 *   <li>Online-Validierung der USt-IdNr beim Bzst (separater Block).</li>
 *   <li>Storno-/Korrekturbelege — diese haben eigene Beleg-Regeln (negierte Beträge,
 *       Pflicht-Verweis auf Originalrechnung), die hier nicht greifen.</li>
 * </ul>
 *
 * Toleranz für Beträge: 1 Cent. Damit fängt der Validator typische Rundungs-
 * differenzen aus dezimaler Arithmetik ab, ohne echte Inkonsistenzen zu schlucken.
 */
@Service
public class InvoiceComplianceValidator {

    private static final BigDecimal AMOUNT_TOLERANCE = new BigDecimal("0.01");

    /**
     * Wirft {@link InvoiceComplianceException} mit allen gefundenen Verstößen,
     * wenn die Rechnung nicht festschreibungsreif ist. Andernfalls passiert nichts.
     */
    public void validateForIssuance(CustomerInvoice invoice) {
        if (invoice == null) {
            throw new IllegalArgumentException("Rechnung darf nicht null sein.");
        }
        if (invoice.getType() != null && invoice.getType() != InvoiceType.INVOICE) {
            // Storno/Korrektur folgen anderen Regeln und werden hier nicht geprüft.
            return;
        }
        List<String> violations = collectViolations(invoice);
        if (!violations.isEmpty()) {
            throw new InvoiceComplianceException(violations);
        }
    }

    private List<String> collectViolations(CustomerInvoice invoice) {
        List<String> violations = new ArrayList<>();
        checkInvoiceNumber(invoice, violations);
        checkDates(invoice, violations);
        checkSender(invoice, violations);
        checkRecipient(invoice, violations);
        checkItems(invoice, violations);
        checkAmounts(invoice, violations);
        checkVatNotices(invoice, violations);
        return violations;
    }

    private void checkInvoiceNumber(CustomerInvoice invoice, List<String> violations) {
        if (isBlank(invoice.getInvoiceNumber())) {
            violations.add("Rechnungsnummer fehlt (§ 14 Abs. 4 Nr. 4 UStG).");
        }
    }

    private void checkDates(CustomerInvoice invoice, List<String> violations) {
        if (invoice.getInvoiceDate() == null) {
            violations.add("Rechnungsdatum (Ausstellungsdatum) fehlt (§ 14 Abs. 4 Nr. 3 UStG).");
        }
        if (invoice.getDeliveryDate() == null) {
            violations.add("Leistungsdatum fehlt (§ 14 Abs. 4 Nr. 6 UStG). "
                    + "Bei zeitgleicher Leistung kann es dem Rechnungsdatum entsprechen — muss aber gesetzt sein.");
        }
    }

    private void checkSender(CustomerInvoice invoice, List<String> violations) {
        if (isBlank(invoice.getSenderName())) {
            violations.add("Name des Leistenden (Absender) fehlt (§ 14 Abs. 4 Nr. 1 UStG).");
        }
        if (isBlank(invoice.getSenderAddress()) || isBlank(invoice.getSenderPostcode())
                || isBlank(invoice.getSenderCity())) {
            violations.add("Vollständige Anschrift des Leistenden (Straße, PLZ, Ort) fehlt (§ 14 Abs. 4 Nr. 1 UStG).");
        }
        if (isBlank(invoice.getSenderTaxNumber()) && isBlank(invoice.getSenderVatId())) {
            violations.add("Steuernummer oder USt-IdNr des Leistenden fehlt (§ 14 Abs. 4 Nr. 2 UStG).");
        }
    }

    private void checkRecipient(CustomerInvoice invoice, List<String> violations) {
        if (isBlank(invoice.getRecipientName())) {
            violations.add("Name des Leistungsempfängers fehlt (§ 14 Abs. 4 Nr. 1 UStG).");
        }
        if (isBlank(invoice.getRecipientAddress()) || isBlank(invoice.getRecipientPostcode())
                || isBlank(invoice.getRecipientCity())) {
            violations.add("Vollständige Anschrift des Leistungsempfängers (Straße, PLZ, Ort) fehlt "
                    + "(§ 14 Abs. 4 Nr. 1 UStG).");
        }
    }

    private void checkItems(CustomerInvoice invoice, List<String> violations) {
        List<CustomerInvoiceItem> items = invoice.getItems();
        if (items == null || items.isEmpty()) {
            violations.add("Keine Positionen erfasst — Menge und Art der Leistung sind erforderlich "
                    + "(§ 14 Abs. 4 Nr. 5 UStG).");
            return;
        }
        for (int i = 0; i < items.size(); i++) {
            CustomerInvoiceItem item = items.get(i);
            int rowNumber = i + 1;
            if (item == null) {
                violations.add("Position " + rowNumber + ": leere Position.");
                continue;
            }
            if (isBlank(item.getDescription())) {
                violations.add("Position " + rowNumber + ": Bezeichnung der Leistung fehlt.");
            }
            if (item.getQuantity() == null || item.getQuantity().signum() <= 0) {
                violations.add("Position " + rowNumber + ": Menge muss größer 0 sein.");
            }
            if (item.getUnitPrice() == null || item.getUnitPrice().signum() < 0) {
                violations.add("Position " + rowNumber + ": Einzelpreis fehlt oder ist negativ.");
            }
        }
    }

    private void checkAmounts(CustomerInvoice invoice, List<String> violations) {
        BigDecimal net = invoice.getNetAmount();
        BigDecimal vat = invoice.getVatAmount();
        BigDecimal total = invoice.getTotalAmount();
        if (net == null || vat == null || total == null) {
            violations.add("Beträge unvollständig: Netto, Steuerbetrag und Bruttobetrag müssen ausgewiesen sein "
                    + "(§ 14 Abs. 4 Nr. 7 + 8 UStG).");
            return;
        }
        if (net.add(vat).subtract(total).abs().compareTo(AMOUNT_TOLERANCE) > 0) {
            violations.add("Bruttobetrag passt nicht zu Netto + Steuerbetrag (Differenz > 1 Cent).");
        }
        if (invoice.getItems() != null && !invoice.getItems().isEmpty()) {
            BigDecimal sumItems = invoice.getItems().stream()
                    .map(CustomerInvoiceItem::getNetTotal)
                    .filter(java.util.Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            if (sumItems.subtract(net).abs().compareTo(AMOUNT_TOLERANCE) > 0) {
                violations.add("Summe der Positionen (netto) " + sumItems + " weicht vom Rechnungs-Netto " + net
                        + " ab (Differenz > 1 Cent).");
            }
        }
    }

    private void checkVatNotices(CustomerInvoice invoice, List<String> violations) {
        BigDecimal rate = invoice.getVatRate();
        if (rate == null) {
            violations.add("Steuersatz fehlt (§ 14 Abs. 4 Nr. 8 UStG).");
            return;
        }
        if (rate.signum() == 0) {
            // Bei 0 % USt verlangt das UStG einen erklärenden Hinweis — entweder
            // Reverse-Charge (§ 13b), Kleinunternehmerregelung (§ 19), eine
            // innergemeinschaftliche Lieferung (§ 6a) oder eine andere Steuerbefreiung.
            // Ohne Hinweis ist eine 0 %-Rechnung formal mangelhaft.
            boolean hasNotice = !isBlank(invoice.getReverseChargeNote()) || !isBlank(invoice.getLegalNotes());
            if (!hasNotice) {
                violations.add("Bei 0 % USt ist ein rechtlicher Hinweis erforderlich "
                        + "(z.B. \"Steuerschuldnerschaft des Leistungsempfängers\" nach § 13b UStG, "
                        + "Kleinunternehmerregelung § 19 UStG oder Steuerbefreiung). "
                        + "Bitte im Feld \"Reverse-Charge-Hinweis\" oder \"Rechtliche Hinweise\" ergänzen.");
            }
        } else {
            BigDecimal expectedVat = invoice.getNetAmount() != null
                    ? invoice.getNetAmount().multiply(rate).setScale(2, RoundingMode.HALF_UP)
                    : null;
            if (expectedVat != null && invoice.getVatAmount() != null
                    && expectedVat.subtract(invoice.getVatAmount()).abs().compareTo(AMOUNT_TOLERANCE) > 0) {
                violations.add("Ausgewiesener Steuerbetrag " + invoice.getVatAmount()
                        + " passt nicht zu Netto × Steuersatz (erwartet " + expectedVat + ").");
            }
        }
    }

    private boolean isBlank(String value) {
        return value == null || value.isBlank();
    }
}
