package de.accso.flexinale.common.infrastructure.eventbus;

import com.fasterxml.jackson.core.JsonProcessingException;
import de.accso.flexinale.common.api.event.AbstractEvent;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.api.event.EventContext;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.junit.jupiter.api.Test;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

import static org.assertj.core.api.Assertions.assertThat;

class EventSerializationHelperTest {

    @Test
    void testSerializeEventClass2JsonString() throws JsonProcessingException {
        // arrange
        MyNumberEventImplementedAsSubclassOfAbstractEvent myNumberEvent =
                new MyNumberEventImplementedAsSubclassOfAbstractEvent(21, 42);

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

        // assert
        String expectedJsonString =
                ("{\"id\":{\"id\":\"%s\"}," +
                        "\"correlationId\":{\"id\":\"%s\"}," +
                        "\"timestamp\":\"%s\"," +
                        "\"eventContext\":{\"correlationId\":{\"id\":\"%s\"}," +
                        "\"eventHistory\":[{\"id\":{\"id\":\"%s\"}," +
                        "\"eventType\":\"%s\"}]}," +
                        "\"value1\":21,\"value2\":42,\"version\":{\"version\":0}}")
                .formatted(myNumberEvent.id().id(),
                        myNumberEvent.correlationId().id(),
                        timestamp2String(myNumberEvent.timestamp()),
                        myNumberEvent.correlationId().id(),
                        myNumberEvent.id().id(),
                        MyNumberEventImplementedAsSubclassOfAbstractEvent.class.getName());

        assertThat(jsonString).isEqualTo(expectedJsonString);
    }

    @Test
    void testSerializeEventRecord2JsonString() throws JsonProcessingException {
        // arrange
        MyNumberEventImplementedAsRecord myNumberEvent =
                new MyNumberEventImplementedAsRecord(
                        Identifiable.Id.of(), Versionable.initialVersion(), LocalDateTime.now(), Identifiable.Id.of(), null,
                        21, 42);

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

        // assert
        String expectedJsonString = ("{\"id\":{\"id\":\"%s\"}," +
                        "\"version\":{\"version\":0}," +
                        "\"timestamp\":\"%s\"," +
                        "\"correlationId\":{\"id\":\"%s\"}," +
                        "\"eventContext\":null,\"value1\":21,\"value2\":42}")
                .formatted(myNumberEvent.id().id(),
                        timestamp2String(myNumberEvent.timestamp()),
                        myNumberEvent.correlationId().id());

        assertThat(jsonString).isEqualTo(expectedJsonString);
    }

    @Test
    void testDeserializeJsonString2EventClass() throws JsonProcessingException {
        // arrange
        MyNumberEventImplementedAsSubclassOfAbstractEvent myNumberEvent =
                new MyNumberEventImplementedAsSubclassOfAbstractEvent(21, 42);
        String jsonString = EventSerializationHelper.serializeEvent2JsonString(myNumberEvent);

        // act
        MyNumberEventImplementedAsSubclassOfAbstractEvent deserializedEvent =
                EventSerializationHelper.deserializeJsonString2Event(
                        jsonString, MyNumberEventImplementedAsSubclassOfAbstractEvent.class);

        // assert
        assertThat(deserializedEvent).isEqualTo(myNumberEvent);
    }

    @Test
    void testDeserializeJsonString2EventRecord() throws JsonProcessingException {
        // arrange
        MyNumberEventImplementedAsRecord myNumberEvent =
                new MyNumberEventImplementedAsRecord(
                        Identifiable.Id.of(), Versionable.initialVersion(), LocalDateTime.now(), Identifiable.Id.of(), null,
                        21, 42);
        String jsonString = EventSerializationHelper.serializeEvent2JsonString(myNumberEvent);

        // act
        MyNumberEventImplementedAsRecord deserializedEvent =
                EventSerializationHelper.deserializeJsonString2Event(
                        jsonString, MyNumberEventImplementedAsRecord.class);

        // assert
        assertThat(deserializedEvent).isEqualTo(myNumberEvent);
    }

    private static String timestamp2String(final LocalDateTime timestamp) {
        return timestamp.format(DateTimeFormatter.ISO_DATE_TIME);
    }

}

final class MyNumberEventImplementedAsSubclassOfAbstractEvent extends AbstractEvent {
    public Integer value1;
    public Integer value2;

    private final Version version = Versionable.initialVersion();

    // needed for Jackson de/serialization
    private MyNumberEventImplementedAsSubclassOfAbstractEvent() {}

    public MyNumberEventImplementedAsSubclassOfAbstractEvent(final Integer value1, final Integer value2) {
        this.value1 = value1;
        this.value2 = value2;
    }

    public MyNumberEventImplementedAsSubclassOfAbstractEvent(final Event predecessorEvent, final Integer value1, final Integer value2) {
        super(predecessorEvent);
        this.value1 = value1;
        this.value2 = value2;
    }

    @Override
    public boolean equals(final Object o) {
        if (this == o) {return true;}

        if (o == null || getClass() != o.getClass()) {return false;}

        final MyNumberEventImplementedAsSubclassOfAbstractEvent that = (MyNumberEventImplementedAsSubclassOfAbstractEvent) o;

        return new EqualsBuilder()
                .appendSuper(super.equals(o))
                .append(this.version(), that.version())
                .append(value1, that.value1)
                .append(value2, that.value2)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37)
                .appendSuper(super.hashCode())
                .append(this.version())
                .append(value1).append(value2)
                .toHashCode();
    }

    @Override
    public Version version() {
        return version;
    }
}


@SuppressWarnings("ALL")
record MyNumberEventImplementedAsRecord(
        Id id, Version version, LocalDateTime timestamp, Id correlationId, EventContext eventContext,
        Integer value1, Integer value2) implements Event { }