package de.accso.flexinale.besucherportal.api_in.web;

import de.accso.flexinale.backoffice_api_contract.api_contract.VorfuehrungRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.model.VorfuehrungTO;
import de.accso.flexinale.ticketing_api_contract.api_contract.model.TicketTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;

class TicketSorter {

    private static final Logger LOGGER = LoggerFactory.getLogger(TicketSorter.class);

    /**
     * return a list of list of tickets, sorted by time (inner list) and date (outer list).
     * each list contains the tickets for a day
     */
        public static List<List<VorfuehrungMitAnzahlTicketsTO>> sortAndMapTicketsPerDay(final List<TicketTO> allTickets,
                                                                                        final VorfuehrungRetriever vorfuehrungRetriever) {
        if (allTickets == null || allTickets.isEmpty()) {
            return new ArrayList<>();
        }

        Map<TicketTO, VorfuehrungTO> ticketToVorfuehrung = createTicketToVorfuehrungMap(allTickets, vorfuehrungRetriever);

        // 0) sort tickets by time (not done in database SQL query as before)
        allTickets.sort(new TicketTOByZeitSortingComparator(ticketToVorfuehrung));

        // 1) tickets by day
        List<List<TicketTO>> ticketsProTag = new ArrayList<>();

        LocalDate previousDate = LocalDate.MIN; // far past :-)
        for (TicketTO ticket : allTickets) {
            LocalDateTime ticketZeit = getRawOrNull(ticketToVorfuehrung.get(ticket).zeit());
            LocalDate currentDate = ticketZeit.toLocalDate();

            if (currentDate.equals(previousDate)) {
                ticketsProTag.getLast().add(ticket);
            }
            else {
                List<TicketTO> tickets = new ArrayList<>();
                tickets.add(ticket);
                ticketsProTag.add(tickets);
            }
            previousDate = currentDate;
        }

        // 2) inner structure: Vorfuehrung and tickets
        List<List<VorfuehrungMitAnzahlTicketsTO>> vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag = new ArrayList<>();

        for (List<TicketTO> ticketsForOneDay : ticketsProTag) {
            List<VorfuehrungMitAnzahlTicketsTO> vorfuehrungenUndAnzahlTicketsFuerEinenTag = new ArrayList<>();
            int anzahlTicketsFuerVorfuehrung = 0;
            VorfuehrungTO previousVorfuehrung = ticketToVorfuehrung.get(ticketsForOneDay.getFirst());

            for (TicketTO ticket : ticketsForOneDay) {
                // inside each day, vorfuehrungen are ordered by zeit.
                // There is never more than one vorfuehrung at a zeit for a user.
                // So tickets for the same vorfuehrung are all in a row - that's why the following works.
                VorfuehrungTO currentVorfuehrung = ticketToVorfuehrung.get(ticket);

                if (currentVorfuehrung.equals(previousVorfuehrung)) {
                    anzahlTicketsFuerVorfuehrung++;
                }
                else {
                    // add the information for previous Vorfuehrung
                    VorfuehrungMitAnzahlTicketsTO to = mapVorfuehrungToTO(
                            anzahlTicketsFuerVorfuehrung, previousVorfuehrung);
                    vorfuehrungenUndAnzahlTicketsFuerEinenTag.add(to);
                    anzahlTicketsFuerVorfuehrung = 1; // ... we also have a new ticket for the next Vorfuehrung
                }

                previousVorfuehrung = currentVorfuehrung;
            }

            // Also add the last Vorfuehrung an anzahlTickets when day is over
            VorfuehrungMitAnzahlTicketsTO to = mapVorfuehrungToTO(
                    anzahlTicketsFuerVorfuehrung, previousVorfuehrung);
            vorfuehrungenUndAnzahlTicketsFuerEinenTag.add(to);
            vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag.add(vorfuehrungenUndAnzahlTicketsFuerEinenTag);
        }
        return vorfuehrungUndAnzahlTicketsFuerVorfuehrungProTag;
    }

    private static VorfuehrungMitAnzahlTicketsTO mapVorfuehrungToTO(final int anzahlTicketsFuerVorfuehrung,
                                                                    final VorfuehrungTO vorfuehrung) {
        return new VorfuehrungMitAnzahlTicketsTO(
                new VorfuehrungMitAnzahlTicketsTO.Zeit(getRawOrNull(vorfuehrung.zeit())),
                vorfuehrung.film(),
                vorfuehrung.kinoSaal(),
                new VorfuehrungMitAnzahlTicketsTO.AnzahlTickets(anzahlTicketsFuerVorfuehrung)
        );
    }

    private static Map<TicketTO, VorfuehrungTO> createTicketToVorfuehrungMap(final List<TicketTO> allTickets, final VorfuehrungRetriever vorfuehrungRetriever) {
        Map<TicketTO, VorfuehrungTO> ticketToVorfuehrung = new HashMap<>();
        for (TicketTO ticket : allTickets) {
           VorfuehrungTO vorfuehrung = vorfuehrungRetriever.vorfuehrung(ticket.vorfuehrungId());
            if (vorfuehrung == null) {
                LOGGER.error("Vorfuehrung %s  of ticket %s could not be found.".formatted(ticket.vorfuehrungId(), ticket.id()));
            }
            else {
                ticketToVorfuehrung.put(ticket, vorfuehrung);
            }
        }
        return ticketToVorfuehrung;
    }
}

class TicketTOByZeitSortingComparator implements Comparator<TicketTO>, Serializable {

    private final Map<TicketTO, VorfuehrungTO> ticketToVorfuehrung;

    TicketTOByZeitSortingComparator(final Map<TicketTO, VorfuehrungTO> ticketToVorfuehrung) {
        this.ticketToVorfuehrung = ticketToVorfuehrung;
    }

    @Override
    public int compare(final TicketTO t1, final TicketTO t2) {
        VorfuehrungTO v1 = ticketToVorfuehrung.get(t1);
        VorfuehrungTO v2 = ticketToVorfuehrung.get(t2);
        LocalDateTime v1Zeit = getRawOrNull(v1.zeit());
        LocalDateTime v2Zeit = getRawOrNull(v2.zeit());
        return v1Zeit.compareTo(v2Zeit);
    }
}