package de.accso.flexinale.application.services;

import de.accso.flexinale.FlexinaleModulith1OnionApplication;
import de.accso.flexinale.domain.dao.*;
import de.accso.flexinale.domain.model.*;
import de.accso.flexinale.domain.services.TicketService;
import de.accso.flexinale.infrastructure.security.BenutzerDao;
import de.accso.flexinale.infrastructure.security.mapper.BenutzerEntity2BesucherMapper;
import de.accso.flexinale.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.shared_code_for_test.TestDataBuilder;
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 org.assertj.core.api.Assertions.assertThat;

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

    @Autowired
    TicketService ticketService;

    @Autowired
    private BenutzerDao benutzerDao;

    @Autowired
    private FilmDao filmDao;

    @Autowired
    private KinoDao kinoDao;

    @Autowired
    private TicketDao ticketDao;

    @Autowired
    private VorfuehrungDao vorfuehrungDao;

    @Autowired
    private KontingentDao kontingentDao;

    private Film film;
    private Kino kino;
    private Vorfuehrung vorfuehrung;
    private Besucher besucher;

    @BeforeEach
    void prepareDataForTicketKauf() {
        // arrange for all tests

        film = TestDataBuilder.createFilm();
        filmDao.save(film);

        kino = TestDataBuilder.createKinoWithKinosaele(120);
        kinoDao.save(kino);
        KinoSaal kinoSaal = kino.kinoSaele.iterator().next();

        vorfuehrung = TestDataBuilder.createVorfuehrung(LocalDateTime.now(), film, kinoSaal, 33);
        vorfuehrungDao.save(vorfuehrung);

        besucher = TestDataBuilder.createBesucher();
        benutzerDao.save(BenutzerEntity2BesucherMapper.map(besucher));
    }

    @AfterEach
    void cleanupDatabase() {
        ticketDao.deleteAll();
        vorfuehrungDao.deleteAll();
        filmDao.deleteAll();
        kinoDao.deleteAll();
        benutzerDao.deleteAll();
    }

    @Test
    void testLoeseGutscheineOnlineFuerVorfuehrungEin_Ticket() {
        // arrange
        int anzahlGutscheine = 10;
        // act
        ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung.id(), besucher, anzahlGutscheine);
        // assert
        int anzahlTickets = ticketDao.gesamtZahlDerTicketsFuer(besucher);
        assertThat(anzahlTickets).isEqualTo(anzahlGutscheine); // 1 Ticket hat mehrere Plätze
        List<Ticket> tickets = ticketDao.findByBesucherOrderByZeit(besucher);
        assertThat(tickets.size()).isEqualTo(anzahlGutscheine);
    }

    @Test
    void testLoeseGutscheineOnlineFuerVorfuehrungEin_Kontingent() {
        // arrange
        int kontingent = 120 / 3; // 40
        int anzahlGutscheine = 10;
        // act
        ticketService.loeseGutscheineOnlineFuerVorfuehrungEin(vorfuehrung.id(), besucher, anzahlGutscheine);
        // assert
        Optional<Kontingent> optionalKontingent = kontingentDao.findById(vorfuehrung.getKontingent().id());
        assertThat(optionalKontingent.isPresent()).isTrue();
        assertThat(optionalKontingent.get().getRestKontingentOnline().raw()).isEqualTo(kontingent - anzahlGutscheine); // expected: 40-10 = 30
    }

    @Test
    void saveAndDeleteTicket() {
        // arrange
        Ticket ticket = TestDataBuilder.createTicket(film, vorfuehrung, besucher, Ticket.VerkaufsKanal.KINOKASSE);

        // act
        ticket = ticketDao.save(ticket);

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

        // cleanup
        ticketDao.deleteById(ticket.id());
        vorfuehrungDao.deleteById(vorfuehrung.id());
        kinoDao.deleteById(kino.id());
        filmDao.deleteById(film.id());
        benutzerDao.deleteById(besucher.id());

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