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

import de.accso.flexinale.FlexinaleDistributedApplicationTestIntegrated;
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.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.application.services.TicketService;
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 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.ActiveProfiles;
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 = {FlexinaleDistributedApplicationTestIntegrated.class})
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
@ActiveProfiles("test-integrated")
@ExtendWith(SpringExtension.class)
@DoNotCheckInArchitectureTests
class TicketCRUDTest {

    @Autowired
    TicketService ticketService;

    @Autowired
    private FilmDao filmDao;

    @Autowired
    private KinoDao kinoDao;

    @Autowired
    private TicketDao ticketDao;

    @Autowired
    private VorfuehrungDao vorfuehrungDao;

    @Autowired
    private KontingentDao kontingentDao;

    private BenutzerEntity besucher;

    @BeforeEach
    void prepareDataForTicketKauf() {
        // arrange for all tests
        besucher = TestDataBuilder.createBenutzerEntity();
    }

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

    @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;
        int quoteOnline = 33;
        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(), besucher.id(), 2);
        ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung2.id(), film.id(), besucher.id(), 3);
        ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung3.id(), film.id(), besucher.id(), 4);

        List<Ticket> tickets = ticketService.findByBesucher(besucher.id());

        // 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 testSaveAndDeleteTicket() {
        // arrange
        Film film = TestDataBuilder.createFilm();
        Kino kino = TestDataBuilder.createKinoWithKinosaele(100);
        KinoSaal kinoSaal = kino.kinoSaele.iterator().next();
        Vorfuehrung vorfuehrung = TestDataBuilder.createVorfuehrung(LocalDateTime.now(), film, kinoSaal);

        Ticket ticket = TestDataBuilder.createTicket(film, vorfuehrung, besucher, 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();
    }
}