package de.accso.flexinale.ticketing.domain.services;

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.Vorfuehrung;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.security.infrastructure.persistence.BenutzerEntity;
import de.accso.flexinale.common.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.shared_code_for_test.TestDataBuilder;
import de.accso.flexinale.ticketing.domain.dao.KontingentDao;
import de.accso.flexinale.ticketing.domain.dao.TicketDao;
import de.accso.flexinale.ticketing.domain.model.Kontingent;
import de.accso.flexinale.ticketing.domain.model.Ticket;
import de.accso.flexinale.ticketing.domain.services.TicketService;
import org.junit.jupiter.api.AfterEach;
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 java.util.Optional;

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

@SpringBootTest(classes = {FlexinaleModulith2ComponentsApplication.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
@ExtendWith(SpringExtension.class)
@DoNotCheckInArchitectureTests
class TicketServiceTest {

    @Autowired
    TicketService ticketService;

    @Autowired
    private FilmDao filmDao;

    @Autowired
    private KinoDao kinoDao;

    @Autowired
    private TicketDao ticketDao;

    @Autowired
    private VorfuehrungDao vorfuehrungDao;

    @Autowired
    private KontingentDao kontingentDao;

    private Identifiable.Id besucherId;
    private final int quoteOnline = 33;

    @BeforeEach
    void prepareDataForTicketKauf() {
        // arrange for all tests
        besucherId = Identifiable.Id.of();
    }

    @AfterEach
    void cleanupDatabase() {
        ticketDao.deleteAll();
        kontingentDao.deleteAll();
    }

    @Test
    void testLoeseGutscheineOnlineFuerVorfuehrungEin_Ticket() {
        // arrange
        Vorfuehrung vorfuehrung = TestDataBuilder.createVorfuehrung();
        Identifiable.Id filmId = vorfuehrung.film.id();
        int kontingentGesamt = 120;
        Kontingent kontingent = TestDataBuilder.createKontingent(kontingentGesamt, quoteOnline, vorfuehrung);
        kontingentDao.save(kontingent);

        int anzahlGutscheine = 10;

        // act
        ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung.id(), filmId, besucherId, anzahlGutscheine);

        // assert
        int anzahlTickets = ticketDao.gesamtZahlDerTicketsFuer(besucherId);
        assertThat(anzahlTickets).isEqualTo(anzahlGutscheine);

        List<Ticket> tickets = ticketDao.findAll();
        assertThat(tickets.size()).isEqualTo(anzahlGutscheine);
     }

    @Test
    void testLoeseGutscheineOnlineFuerVorfuehrungEin_Kontingent() {
        // arrange
        Vorfuehrung vorfuehrung = TestDataBuilder.createVorfuehrung();
        Identifiable.Id filmId = vorfuehrung.film.id();
        int kontingentGesamt = 120;
        Kontingent kontingent = TestDataBuilder.createKontingent(kontingentGesamt, quoteOnline, vorfuehrung);

        kontingentDao.save(kontingent);

        int anzahlGutscheine = 10;

        // act
        ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung.id(), filmId, besucherId, anzahlGutscheine);

        // assert
        Optional<Kontingent> kontingent1 = kontingentDao.findById(kontingent.id());
        assertThat(kontingent1.isPresent()).isTrue();
        assertThat(kontingent1.get().getRestKontingentOnline().raw()).isEqualTo(40 - anzahlGutscheine);
     }

    @Test
    void testFindByBesucherOrderByZeit() {
        // arrange
        Film film = TestDataBuilder.createFilm();
        filmDao.save(film);

        Kino kino = TestDataBuilder.createKinoWithKinosaele(120, 300, 600);
        kinoDao.save(kino);

        Vorfuehrung vorfuehrung1 = TestDataBuilder.createVorfuehrung(LocalDateTime.now(), film, kino.kinoSaele.iterator().next());
        Vorfuehrung vorfuehrung2 = TestDataBuilder.createVorfuehrung(LocalDateTime.now().plusHours( 50), film, kino.kinoSaele.iterator().next());
        Vorfuehrung vorfuehrung3 = TestDataBuilder.createVorfuehrung(LocalDateTime.now().plusHours(100), film, kino.kinoSaele.iterator().next());

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

        // arrange
        int kontingentGesamt = 120;
        Kontingent kontingent1 = TestDataBuilder.createKontingent(kontingentGesamt, quoteOnline, vorfuehrung1);
        Kontingent kontingent2 = TestDataBuilder.createKontingent(kontingentGesamt, quoteOnline, vorfuehrung2);
        Kontingent kontingent3 = TestDataBuilder.createKontingent(kontingentGesamt, quoteOnline, vorfuehrung3);

        kontingentDao.save(kontingent1);
        kontingentDao.save(kontingent2);
        kontingentDao.save(kontingent3);

        // act
        ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung1.id(), film.id(), besucherId, 2);
        ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung2.id(), film.id(), besucherId, 3);
        ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung3.id(), film.id(), besucherId, 4);

        List<Ticket> tickets = ticketService.findByBesucherOrderByZeit(besucherId);

        // assert
        assertThat(tickets.size()).isEqualTo(9);

        // all tickets are ordered by zeit
        List<Vorfuehrung> vorfuehrungen = tickets.stream()
                .map(t -> vorfuehrungDao.findById(t.vorfuehrungId).get())
                .toList();

        LocalDateTime lastZeit = LocalDateTime.MIN;
        for (Vorfuehrung v : vorfuehrungen) {
            LocalDateTime zeit = getRawOrNull(v.zeit);
            assertThat(zeit).isAfterOrEqualTo(lastZeit);
            lastZeit = zeit;
        }
    }

    @Test
    void saveAndDeleteTicket() {
        // arrange
        BenutzerEntity benutzer = TestDataBuilder.createBenutzerEntity();
        Vorfuehrung vorfuehrung = TestDataBuilder.createVorfuehrung();

        Ticket ticket = TestDataBuilder.createTicket(vorfuehrung.film, vorfuehrung, benutzer, Ticket.VerkaufsKanal.KINOKASSE);

        // act
        ticketDao.save(ticket);

        // assert
        Optional<Ticket> ticketOptional = ticketDao.findById(ticket.id());
        assertThat(ticketOptional).isNotEmpty();

        // cleanup
        ticketDao.deleteById(ticket.id());

        // assert
        assertThat(ticketDao.findById(ticket.id())).isEmpty();
    }
}