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

import de.accso.flexinale.FlexinaleModulith2ComponentsApplication;
import de.accso.flexinale.backoffice.domain.dao.FilmDao;
import de.accso.flexinale.backoffice.domain.dao.KinoDao;
import de.accso.flexinale.backoffice.domain.dao.VorfuehrungDao;
import de.accso.flexinale.backoffice.domain.model.Film;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.backoffice_api_contract.api_contract.FilmRetriever;
import de.accso.flexinale.backoffice_api_contract.api_contract.VorfuehrungRetriever;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.shared_code_for_test.TestDataBuilder;
import de.accso.flexinale.ticketing_api_contract.api_contract.TicketRetriever;
import de.accso.flexinale.ticketing_api_contract.api_contract.Ticketing;
import de.accso.flexinale.ticketing_api_contract.api_contract.model.TicketTO;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.junit.jupiter.SpringExtension;

import java.time.LocalDateTime;
import java.util.List;

import static de.accso.flexinale.common.shared_kernel.RawWrapper.getRawOrNull;
import static de.accso.flexinale.shared_code_for_test.TestDataBuilder.createFilm;
import static de.accso.flexinale.shared_code_for_test.TestDataBuilder.createKinoWithKinosaele;
import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(classes = {FlexinaleModulith2ComponentsApplication.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
@ExtendWith(SpringExtension.class)
@DoNotCheckInArchitectureTests
class TicketSortingTest {
    @Autowired
    private TicketRetriever ticketRetriever;

    @Autowired
    private Ticketing ticketing;

    @Autowired
    private VorfuehrungDao vorfuehrungDao;

    @Autowired
    private KinoDao kinoDao;

    @Autowired
    private FilmDao filmDao;

    @Autowired
    private VorfuehrungRetriever vorfuehrungRetriever;

    @Autowired
    private FilmRetriever filmRetriever;

    private Identifiable.Id besucherId;

    @BeforeEach
    void prepareTickets() {
        // arrange for all tests
        Film film = createFilm();
        filmDao.save(film);

        Kino kino = createKinoWithKinosaele(120);
        KinoSaal kinoSaal = kino.kinoSaele.iterator().next();

        kinoDao.save(kino);

        LocalDateTime beginnVorfuehrung1 = LocalDateTime.of(2022, 11, 29, 16, 0);

        Vorfuehrung vorfuehrung1 = TestDataBuilder.createVorfuehrung(beginnVorfuehrung1, film, kinoSaal);
        Vorfuehrung vorfuehrung2 = TestDataBuilder.createVorfuehrung(beginnVorfuehrung1.plusHours(24), film, kinoSaal);
        Vorfuehrung vorfuehrung3 = TestDataBuilder.createVorfuehrung(beginnVorfuehrung1.plusHours( 3), film, kinoSaal);

        vorfuehrungDao.save(vorfuehrung1);
        vorfuehrungDao.save(vorfuehrung2);
        vorfuehrungDao.save(vorfuehrung3);

        ticketing.gebeVorfuehrungInDenVerkauf(vorfuehrung1.id(), getRawOrNull(vorfuehrung1.kinoSaal.anzahlPlaetze));
        ticketing.gebeVorfuehrungInDenVerkauf(vorfuehrung2.id(), getRawOrNull(vorfuehrung2.kinoSaal.anzahlPlaetze));
        ticketing.gebeVorfuehrungInDenVerkauf(vorfuehrung3.id(), getRawOrNull(vorfuehrung3.kinoSaal.anzahlPlaetze));

        besucherId = Identifiable.Id.of();

        ticketing.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung1.id(), vorfuehrung1.film.id(), besucherId, 2);
        ticketing.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung2.id(), vorfuehrung2.film.id(), besucherId, 3);
        ticketing.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung3.id(), vorfuehrung3.film.id(), besucherId, 4);
    }

    @Test
    void testSortAndMapTicketsPerDay() {
        List<TicketTO> tickets = ticketRetriever.ticketsByBesucherOrderByZeit(besucherId);
        assertThat(tickets.size()).isEqualTo(9);

        List<List<VorfuehrungMitAnzahlTicketsTO>> vorfuehrungenTickets
                = TicketSorter.sortAndMapTicketsPerDay(tickets, vorfuehrungRetriever);

        // The outer list contains one list for each day. There are tickets for 2 different days
        assertThat(vorfuehrungenTickets.size()).isEqualTo(2);

        // assert order by Zeit
        VorfuehrungMitAnzahlTicketsTO last = null;
        for (VorfuehrungMitAnzahlTicketsTO vMATTO: vorfuehrungenTickets.stream()
                .flatMap(inner -> inner.stream()).toList())
        {
            if (last != null) {
                LocalDateTime lastZeit = getRawOrNull(last.zeit());
                LocalDateTime vMATTOZeit = getRawOrNull(vMATTO.zeit());
                assertThat(lastZeit).isBeforeOrEqualTo(vMATTOZeit);
            }
            last = vMATTO;
        }

        // Day 1: 2 Vorfuehrungen a 2 resp 3 tickets
        List<VorfuehrungMitAnzahlTicketsTO> vorfuehrungenOfDay1 = vorfuehrungenTickets.getFirst();

        assertThat(vorfuehrungenOfDay1.size()).isEqualTo(2);
        LocalDateTime zeit0 = getRawOrNull(vorfuehrungenOfDay1.get(0).zeit());
        LocalDateTime zeit1 = getRawOrNull(vorfuehrungenOfDay1.get(1).zeit());
        assertThat(zeit0).isBeforeOrEqualTo(zeit1);

        assertThat(vorfuehrungenOfDay1.get(0).anzahlTickets().raw()).isEqualTo(2);
        assertThat(vorfuehrungenOfDay1.get(1).anzahlTickets().raw()).isEqualTo(4);

        // Day 2: 1 Vorfuehrung
        List<VorfuehrungMitAnzahlTicketsTO> vorfuehrungenOfDay2 = vorfuehrungenTickets.get(1);
        assertThat(vorfuehrungenOfDay2.size()).isEqualTo(1);
        assertThat(vorfuehrungenOfDay2.getFirst().anzahlTickets().raw()).isEqualTo(3);
    }
}