package de.accso.flexinale.infrastructure.persistence;

import de.accso.flexinale.FlexinaleModulith1OnionApplication;
import de.accso.flexinale.domain.dao.*;
import de.accso.flexinale.domain.model.*;
import de.accso.flexinale.infrastructure.security.BenutzerDao;
import de.accso.flexinale.infrastructure.security.BenutzerEntity;
import de.accso.flexinale.infrastructure.security.mapper.BenutzerEntity2BesucherMapper;
import de.accso.flexinale.shared_kernel.DoNotCheckInArchitectureTests;
import de.accso.flexinale.shared_kernel.Identifiable;
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.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

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

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

    @Autowired
    private FilmDao filmDao;

    @Autowired
    private KinoDao kinoDao;

    @Autowired
    private KinoSaalDao kinoSaalDao;

    @Autowired
    private VorfuehrungDao vorfuehrungDao;

    @Autowired
    private BenutzerDao benutzerDao;

    @Autowired
    private TicketDao ticketDao;

    @Test
    void saveAndDeleteFilm() {
        // arrange
        Film film = createFilm();

        // act
        filmDao.save(film);

        // assert
        assertThat(filmDao.findById(film.id())).isNotEmpty();

        // cleanup
        filmDao.deleteById(film.id());

        // assert
        assertThat(filmDao.findById(film.id())).isEmpty();
    }

    @Test
    void saveAndDeleteKinoAndKinoSaele() {
        // arrange
        Kino kino = createKinoWithKinosaele();
        ArrayList<String> kinoSaeleIds = kino.kinoSaele.stream().map(it -> it.id().id()).collect(
                Collectors.toCollection(ArrayList::new));

        // act
        kinoDao.save(kino);

        // assert
        assertThat(kinoDao.findById(kino.id())).isNotEmpty();

        // cleanup
        kinoDao.deleteById(kino.id());

        // assert
        assertThat(kinoDao.findById(kino.id())).isEmpty();
        for (String kinoSaeleId: kinoSaeleIds) {
            assertThat(kinoSaalDao.findById(Identifiable.Id.of(kinoSaeleId))).isEmpty();
        }
    }

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

        Kino kino = createKinoWithKinosaele();
        kino = kinoDao.save(kino);

        LocalDateTime now = LocalDateTime.now();
        KinoSaal kinoSaal = kino.kinoSaele.stream().findFirst().get();
        Vorfuehrung vorfuehrung = createVorfuehrung(now, film, kinoSaal);

        // act
        vorfuehrungDao.save(vorfuehrung);

        // assert
        Optional<Vorfuehrung> optionalSearchedVorfuehrung = vorfuehrungDao.findById(vorfuehrung.id());
        assertThat(optionalSearchedVorfuehrung).isNotEmpty();

        Vorfuehrung searchedVorfuehrung = optionalSearchedVorfuehrung.get();
        assertThat(((LocalDateTime)getRawOrNull(searchedVorfuehrung.zeit)).getDayOfYear()).isEqualTo(now.getDayOfYear());
        assertThat(((LocalDateTime)getRawOrNull(searchedVorfuehrung.zeit)).getMinute()).isEqualTo(now.getMinute());

        Integer anzahlPlaetze = getRawOrNull(kinoSaal.anzahlPlaetze);
        assertThat(searchedVorfuehrung.getKontingent().getRestKontingentOnline().raw()).isEqualTo(
                anzahlPlaetze / 3 // one third
        );

        // cleanup
        vorfuehrungDao.deleteById(vorfuehrung.id());
        kinoDao.deleteById(kino.id());
        filmDao.deleteById(film.id());

        // assert
        assertThat(vorfuehrungDao.findById(vorfuehrung.id())).isEmpty();
    }

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

        Kino kino = createKinoWithKinosaele();
        kino = kinoDao.save(kino);

        LocalDateTime now = LocalDateTime.now();
        KinoSaal kinoSaal = kino.kinoSaele.stream().findFirst().get();
        Vorfuehrung vorfuehrung = createVorfuehrung(now, film, kinoSaal);
        vorfuehrungDao.save(vorfuehrung);

        BenutzerEntity benutzer = createBenutzerEntity();
        benutzerDao.save(benutzer);
        Besucher besucher = BenutzerEntity2BesucherMapper.map(benutzer);

        Ticket ticket = createTicket(film, vorfuehrung, besucher);

        // act
        ticketDao.save(ticket);

        // assert
        Optional<Ticket> optionalSearchedTicket = ticketDao.findById(ticket.id());
        assertThat(optionalSearchedTicket.isPresent()).isTrue();
        Ticket searchedTicket = optionalSearchedTicket.get();
        assertThat(searchedTicket.equalsByContent(ticket)).isTrue();

        // cleanup
        ticketDao.deleteById(ticket.id());
        benutzerDao.deleteById(Identifiable.Id.of(benutzer.id));
        vorfuehrungDao.deleteById(vorfuehrung.id());
        filmDao.deleteById(film.id());
        kinoDao.deleteById(kino.id());

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

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

        Kino kino = createKinoWithKinosaele();
        kino = kinoDao.save(kino);

        LocalDateTime now = LocalDateTime.now();
        KinoSaal kinoSaal = kino.kinoSaele.stream().findFirst().get();
        Vorfuehrung vorfuehrung1 = createVorfuehrung(now, film, kinoSaal);
        Vorfuehrung vorfuehrung2 = createVorfuehrung(now.plusDays(1), film, kinoSaal);
        vorfuehrungDao.save(vorfuehrung1);
        vorfuehrungDao.save(vorfuehrung2);

        BenutzerEntity benutzer = createBenutzerEntity();
        benutzerDao.save(benutzer);
        Besucher besucher = BenutzerEntity2BesucherMapper.map(benutzer);

        Ticket ticket1 = createTicket(film, vorfuehrung1, besucher);
        Ticket ticket2 = createTicket(film, vorfuehrung2, besucher);

        // act
        ticketDao.save(ticket1);
        ticketDao.save(ticket2);

        List<Ticket> tickets = ticketDao.findByBesucherOrderByZeit(besucher);
        assertThat(tickets).hasSize(2);
    }
}
