package de.assecutor.votianlt.service;

import de.assecutor.votianlt.model.invoices.CustomerInvoice;
import de.assecutor.votianlt.model.invoices.InvoiceNumberReservation;
import de.assecutor.votianlt.model.invoices.InvoiceNumberReservationStatus;
import de.assecutor.votianlt.repository.InvoiceNumberReservationRepository;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

/**
 * Verwaltet das Audit der vergebenen Rechnungsnummern. Jede Vergabe aus dem
 * Counter wird als RESERVED protokolliert; dieser Service vollzieht die
 * Status-Übergänge nach (USED beim Festschreiben, VOIDED beim Verwerfen)
 * und liefert Lücken-Reports für die Betriebsprüfung.
 *
 * Pflichtgrundlage: § 14 Abs. 4 Nr. 4 UStG verlangt eine fortlaufende
 * Rechnungsnummer; lückenhafte Nummernkreise sind nur zulässig, wenn jede
 * fehlende Nummer dokumentiert erklärt werden kann (GoBD).
 *
 * Fehler beim Audit-Schreiben werden bewusst nicht propagiert: Die
 * fachliche Operation (Rechnung festschreiben) hat Vorrang. Verlorene
 * USED-Markierungen sind über den Lücken-Report nachträglich rekonstruierbar.
 */
@Service
public class InvoiceNumberAuditService {

    private static final Logger log = LoggerFactory.getLogger(InvoiceNumberAuditService.class);

    private final InvoiceNumberReservationRepository repository;

    public InvoiceNumberAuditService(InvoiceNumberReservationRepository repository) {
        this.repository = repository;
    }

    /**
     * Markiert die Reservierung der übergebenen Rechnung als USED.
     * Wird vom Lifecycle nach erfolgreichem Festschreiben aufgerufen.
     */
    public void markUsed(CustomerInvoice invoice) {
        if (invoice == null || invoice.getInvoiceNumber() == null || invoice.getUserId() == null) {
            return;
        }
        ObjectId userId = parseUserId(invoice.getUserId());
        if (userId == null) {
            return;
        }
        try {
            Optional<InvoiceNumberReservation> existing = repository.findByUserIdAndNumber(userId,
                    invoice.getInvoiceNumber());
            InvoiceNumberReservation reservation = existing.orElseGet(() -> bootstrapReservation(userId, invoice));
            reservation.setStatus(InvoiceNumberReservationStatus.USED);
            reservation.setInvoiceId(invoice.getId());
            reservation.setUsedAt(Instant.now());
            repository.save(reservation);
        } catch (Exception ex) {
            log.warn("USED-Markierung für Nummer {} (Rechnung {}) fehlgeschlagen: {}",
                    invoice.getInvoiceNumber(), invoice.getId(), ex.getMessage(), ex);
        }
    }

    /**
     * Markiert die zur übergebenen Nummer gehörende Reservierung als VOIDED.
     * Pflichtfeld {@code reason} dokumentiert die Erklärung der Lücke.
     */
    public void markVoided(ObjectId userId, String number, String reason) {
        if (userId == null || number == null || number.isBlank()) {
            throw new IllegalArgumentException("userId und number sind Pflichtparameter.");
        }
        if (reason == null || reason.isBlank()) {
            throw new IllegalArgumentException("Grund (reason) ist Pflicht beim Verwerfen einer Reservierung.");
        }
        InvoiceNumberReservation reservation = repository.findByUserIdAndNumber(userId, number)
                .orElseThrow(() -> new InvoiceLifecycleException(
                        "Keine Reservierung für Nummer " + number + " gefunden."));
        if (reservation.getStatus() == InvoiceNumberReservationStatus.USED) {
            throw new InvoiceLifecycleException(
                    "Nummer " + number + " ist bereits einer ausgestellten Rechnung zugeordnet "
                            + "und kann nicht verworfen werden.");
        }
        reservation.setStatus(InvoiceNumberReservationStatus.VOIDED);
        reservation.setVoidReason(reason);
        reservation.setVoidedAt(Instant.now());
        repository.save(reservation);
    }

    /**
     * Liefert alle Reservierungen eines Nutzers in Sequenzreihenfolge.
     * Basis für vollständige Audit-Reports.
     */
    public List<InvoiceNumberReservation> findAll(ObjectId userId) {
        return repository.findByUserIdOrderBySequenceAsc(userId);
    }

    /**
     * Liefert nur die noch nicht verwendeten Reservierungen eines Nutzers
     * (Status RESERVED oder VOIDED). Im Idealfall ist diese Liste leer oder
     * enthält ausschließlich VOIDED-Einträge mit dokumentiertem Grund.
     * Verbleibende RESERVED-Einträge nach abgeschlossenen Vorgängen sind
     * unerklärte Lücken und sollten in der UI hervorgehoben werden.
     */
    public List<InvoiceNumberReservation> findUnused(ObjectId userId) {
        List<InvoiceNumberReservation> all = repository.findByUserIdOrderBySequenceAsc(userId);
        return all.stream()
                .filter(r -> r.getStatus() != InvoiceNumberReservationStatus.USED)
                .toList();
    }

    /**
     * Erzeugt einen Bootstrap-Eintrag für Rechnungen, die ohne vorausgegangene
     * Reservierung festgeschrieben wurden — z.B. Bestandsdaten aus der Zeit
     * vor Einführung des Reservierungs-Audits oder Storno-/Korrekturbelege,
     * deren Nummer extern übergeben wurde. Status wird direkt auf USED gesetzt.
     */
    private InvoiceNumberReservation bootstrapReservation(ObjectId userId, CustomerInvoice invoice) {
        InvoiceNumberReservation reservation = new InvoiceNumberReservation();
        reservation.setUserId(userId);
        reservation.setNumber(invoice.getInvoiceNumber());
        reservation.setSequence(extractSequence(invoice.getInvoiceNumber()));
        reservation.setReservedAt(Instant.now());
        reservation.setReservedBy("system (bootstrap)");
        return reservation;
    }

    /**
     * Best-effort-Extraktion der numerischen Sequenz aus einer formatierten
     * Rechnungsnummer (z.B. „RE-2026-000123" → 123). Liefert -1, wenn keine
     * trailing-Ziffern vorhanden sind — dann ist die Nummer für die
     * Lücken-Sortierung ungeeignet, aber der Audit-Eintrag bleibt erhalten.
     */
    private long extractSequence(String number) {
        if (number == null) {
            return -1L;
        }
        int end = number.length();
        int start = end;
        while (start > 0 && Character.isDigit(number.charAt(start - 1))) {
            start--;
        }
        if (start == end) {
            return -1L;
        }
        try {
            return Long.parseLong(number.substring(start, end));
        } catch (NumberFormatException ex) {
            return -1L;
        }
    }

    private ObjectId parseUserId(String value) {
        if (value == null || value.isBlank()) {
            return null;
        }
        try {
            return new ObjectId(value);
        } catch (IllegalArgumentException ex) {
            log.warn("UserId '{}' ist keine gültige ObjectId — Audit übersprungen.", value);
            return null;
        }
    }
}
