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

import com.fasterxml.jackson.core.JsonProcessingException;
import de.accso.flexinale.besucherportal.api_contract.event.model.GutscheinEinloesenAuftragTO;
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 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.time.LocalDateTime;
import java.util.Set;
import java.util.stream.Stream;

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

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

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

        GutscheinEinloesenAuftragTO gutscheinEinloesenAuftragTO = createGutscheinTO();
        setField(gutscheinEventType, event, "gutscheinEinloesenAuftrag", gutscheinEinloesenAuftragTO, true);

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

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

    private static GutscheinEinloesenAuftragTO createGutscheinTO() {
        GutscheinEinloesenAuftragTO.AnzahlTickets anzahlTickets = new GutscheinEinloesenAuftragTO.AnzahlTickets(42);
        return new GutscheinEinloesenAuftragTO(Identifiable.Id.of(), Versionable.unknownVersion(),
                Identifiable.Id.of(), Identifiable.Id.of(), Identifiable.Id.of(),
                anzahlTickets);
    }

    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 getExpectedJsonStringForRawTypeLocalDateTime(String fieldName, RawWrapper<LocalDateTime> field) {
        return String.format("\"%s\":{\"raw\":\"%s\"", fieldName, field.raw().toString());
    }
}