package de.accso.flexinale.ticketing.api_contract.event;

import com.fasterxml.jackson.core.JsonProcessingException;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.infrastructure.eventbus.EventSerializationHelper;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.RawWrapper;
import de.accso.flexinale.common.shared_kernel.Versionable;
import de.accso.flexinale.ticketing.api_contract.event.model.OnlineKontingentTO;
import de.accso.flexinale.ticketing.api_contract.event.model.TicketTO;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Set;
import java.util.stream.Stream;

import static de.accso.flexinale.ticketing.api_contract.event.ReflectionHelper.setField;
import static org.assertj.core.api.Assertions.assertThat;

class EventSerializationTest {
    private static Stream<Arguments> allOnlineKontingentEventTypes() {
        return Set.of(OnlineKontingentChangedEvent.class).stream().map(Arguments::of);
    }

    private static Stream<Arguments> allTicketEventTypes() {
        return Set.of(TicketGekauftEvent.class, TicketUngueltigEvent.class).stream().map(Arguments::of);
    }

    @ParameterizedTest
    @MethodSource("allOnlineKontingentEventTypes")
    <E extends Event> void testSerializeOnlineKontingentEventClass2JsonString(final Class<E> onlineKontingentEventType)
            throws JsonProcessingException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        // arrange
        Constructor<E> constructor = onlineKontingentEventType.getDeclaredConstructor();
        constructor.setAccessible(true);
        Event event = constructor.newInstance();

        OnlineKontingentTO onlineKontingentTO = createOnlineKontingentTO();
        setField(onlineKontingentEventType, event, "onlineKontingent", onlineKontingentTO, true);

        // act
        String jsonString = EventSerializationHelper.serializeEvent2JsonString(event);

        // assert
        assertThat(jsonString).contains(getExpectedJsonStringForVersion(event.version()));
        assertThat(jsonString).contains(getExpectedJsonStringForRawTypeInteger("neuesOnlineRestKontingent", onlineKontingentTO.neuesOnlineRestKontingent()));
    }

    @ParameterizedTest
    @MethodSource("allTicketEventTypes")
    <E extends Event> void testSerializeTicketEventClass2JsonString(final Class<E> ticketEventType)
            throws JsonProcessingException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
        // arrange
        Constructor<E> constructor = ticketEventType.getDeclaredConstructor();
        constructor.setAccessible(true);
        Event event = constructor.newInstance();

        TicketTO ticketTO = createTicketTO();
        setField(ticketEventType, event, "ticket", ticketTO, true);

        // act
        String jsonString = EventSerializationHelper.serializeEvent2JsonString(event);

        // assert
        assertThat(jsonString).contains(getExpectedJsonStringForVersion(event.version()));
        assertThat(jsonString).contains(getExpectedJsonStringForRawTypeBoolean("gueltig", ticketTO.gueltig()));
    }

    private static OnlineKontingentTO createOnlineKontingentTO() {
        OnlineKontingentTO.RestKontingent restKontingent = new OnlineKontingentTO.RestKontingent(42);
        return new OnlineKontingentTO(Identifiable.Id.of(), Versionable.unknownVersion(), Identifiable.Id.of(), restKontingent);
    }

    private static TicketTO createTicketTO() {
        TicketTO.VerkaufsKanal verkaufsKanal = TicketTO.VerkaufsKanal.ONLINE;
        TicketTO.Gueltig gueltig = new TicketTO.Gueltig(true);
        return new TicketTO(Identifiable.Id.of(), Versionable.unknownVersion(),
                Identifiable.Id.of(), Identifiable.Id.of(), Identifiable.Id.of(),
                verkaufsKanal, gueltig);
    }

    private static String getExpectedJsonStringForVersion(Versionable.Version field) {
        return String.format("\"%s\":{\"version\":%d", "version", field.version());
    }
    private static String getExpectedJsonStringForRawTypeString(String fieldName, RawWrapper<String> field) {
        return String.format("\"%s\":{\"raw\":\"%s\"", fieldName, field.raw());
    }
    private static String getExpectedJsonStringForRawTypeInteger(String fieldName, RawWrapper<Integer> field) {
        return String.format("\"%s\":{\"raw\":%d", fieldName, field.raw());
    }
    private static String getExpectedJsonStringForRawTypeBoolean(String fieldName, RawWrapper<Boolean> field) {
        return String.format("\"%s\":{\"raw\":%s", fieldName, field.raw().toString());
    }
}