package eu.teraflow.automation;

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

import context.ContextOuterClass;
import context.ContextOuterClass.DeviceId;
import context.ContextOuterClass.DeviceOperationalStatusEnum;
import context.ContextOuterClass.Uuid;
import eu.teraflow.automation.context.model.Event;
import eu.teraflow.automation.context.model.EventTypeEnum;
import eu.teraflow.automation.device.model.ConfigActionEnum;
import eu.teraflow.automation.device.model.ConfigRule;
import eu.teraflow.automation.device.model.Device;
import eu.teraflow.automation.device.model.DeviceConfig;
import eu.teraflow.automation.device.model.DeviceEvent;
import eu.teraflow.automation.device.model.DeviceOperationalStatus;
import io.quarkus.test.junit.QuarkusTest;
import java.util.List;
import java.util.stream.Stream;
import javax.inject.Inject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

@QuarkusTest
class SerializerTest {

    @Inject Serializer serializer;

    @Test
    void shouldSerializeDeviceId() {
        final var expectedId = "expectedDeviceId";
        final var expectedUuid = Uuid.newBuilder().setUuid(expectedId).build();
        final var expectedDeviceId = DeviceId.newBuilder().setDeviceUuid(expectedUuid).build();

        final var serializedDeviceId = serializer.serializeDeviceId("expectedDeviceId");

        assertThat(serializedDeviceId).usingRecursiveComparison().isEqualTo(expectedDeviceId);
    }

    @Test
    void shouldDeserializeDeviceId() {
        final var expectedDeviceId = "expectedDeviceId";

        final var serializedDeviceId = serializer.serializeDeviceId("expectedDeviceId");
        final var deviceId = serializer.deserialize(serializedDeviceId);

        assertThat(deviceId).isEqualTo(expectedDeviceId);
    }

    private static Stream<Arguments> provideEventTypeEnum() {
        return Stream.of(
                Arguments.of(EventTypeEnum.CREATE, ContextOuterClass.EventTypeEnum.EVENTTYPE_CREATE),
                Arguments.of(EventTypeEnum.REMOVE, ContextOuterClass.EventTypeEnum.EVENTTYPE_REMOVE),
                Arguments.of(EventTypeEnum.UNDEFINED, ContextOuterClass.EventTypeEnum.EVENTTYPE_UNDEFINED),
                Arguments.of(EventTypeEnum.UPDATE, ContextOuterClass.EventTypeEnum.EVENTTYPE_UPDATE));
    }

    @ParameterizedTest
    @MethodSource("provideEventTypeEnum")
    void shouldSerializeEventType(
            EventTypeEnum eventType, ContextOuterClass.EventTypeEnum expectedSerializedType) {
        final var serializedType = serializer.serialize(eventType);
        assertThat(serializedType.getNumber()).isEqualTo(expectedSerializedType.getNumber());
    }

    @ParameterizedTest
    @MethodSource("provideEventTypeEnum")
    void shouldDeserializeEventType(
            EventTypeEnum expectedEventType, ContextOuterClass.EventTypeEnum serializedEventType) {
        final var eventType = serializer.deserialize(serializedEventType);
        assertThat(eventType).isEqualTo(expectedEventType);
    }

    @Test
    void shouldSerializeEvent() {
        final var expectedEvent =
                ContextOuterClass.Event.newBuilder()
                        .setTimestamp(1)
                        .setEventType(ContextOuterClass.EventTypeEnum.EVENTTYPE_CREATE)
                        .build();

        final var event = new Event(1, EventTypeEnum.CREATE);
        final var serializedEvent = serializer.serialize(event);

        assertThat(serializedEvent).usingRecursiveComparison().isEqualTo(expectedEvent);
    }

    @Test
    void shouldDeserializeEvent() {
        final var expectedEvent = new Event(1, EventTypeEnum.CREATE);

        final var serializedEvent =
                ContextOuterClass.Event.newBuilder()
                        .setTimestamp(1)
                        .setEventType(ContextOuterClass.EventTypeEnum.EVENTTYPE_CREATE)
                        .build();
        final var event = serializer.deserialize(serializedEvent);

        assertThat(event).usingRecursiveComparison().isEqualTo(expectedEvent);
    }

    @Test
    void shouldSerializeDeviceEvent() {
        final var expectedUuid = Uuid.newBuilder().setUuid("deviceId");
        final var expectedDeviceId = DeviceId.newBuilder().setDeviceUuid(expectedUuid).build();
        final var expectedEvent =
                ContextOuterClass.Event.newBuilder()
                        .setTimestamp(1)
                        .setEventType(ContextOuterClass.EventTypeEnum.EVENTTYPE_CREATE)
                        .build();
        final var expectedDeviceEvent =
                ContextOuterClass.DeviceEvent.newBuilder()
                        .setDeviceId(expectedDeviceId)
                        .setEvent(expectedEvent)
                        .build();

        final var creationEvent = new Event(1, EventTypeEnum.CREATE);
        final var deviceEvent = new DeviceEvent("deviceId", creationEvent);
        final var serializedDeviceEvent = serializer.serialize(deviceEvent);

        assertThat(serializedDeviceEvent).usingRecursiveComparison().isEqualTo(expectedDeviceEvent);
    }

    @Test
    void shouldDeserializeDeviceEvent() {
        final var dummyDeviceId = "deviceId";
        final var expectedEventType = EventTypeEnum.REMOVE;
        final var expectedTimestamp = 1;
        final var creationEvent = new Event(expectedTimestamp, expectedEventType);
        final var expectedDeviceEvent = new DeviceEvent(dummyDeviceId, creationEvent);

        final var deviceUuid = Uuid.newBuilder().setUuid("deviceId");
        final var deviceId = DeviceId.newBuilder().setDeviceUuid(deviceUuid).build();
        final var event =
                ContextOuterClass.Event.newBuilder()
                        .setTimestamp(1)
                        .setEventType(ContextOuterClass.EventTypeEnum.EVENTTYPE_REMOVE)
                        .build();
        final var serializedDeviceEvent =
                ContextOuterClass.DeviceEvent.newBuilder().setDeviceId(deviceId).setEvent(event).build();
        final var deviceEvent = serializer.deserialize(serializedDeviceEvent);

        assertThat(deviceEvent).usingRecursiveComparison().isEqualTo(expectedDeviceEvent);
    }

    private static Stream<Arguments> provideConfigActionEnum() {
        return Stream.of(
                Arguments.of(ConfigActionEnum.SET, ContextOuterClass.ConfigActionEnum.CONFIGACTION_SET),
                Arguments.of(
                        ConfigActionEnum.DELETE, ContextOuterClass.ConfigActionEnum.CONFIGACTION_DELETE),
                Arguments.of(
                        ConfigActionEnum.UNDEFINED, ContextOuterClass.ConfigActionEnum.CONFIGACTION_UNDEFINED));
    }

    @ParameterizedTest
    @MethodSource("provideConfigActionEnum")
    void shouldSerializeConfigActionEnum(
            ConfigActionEnum configAction, ContextOuterClass.ConfigActionEnum expectedConfigAction) {
        final var serializedConfigAction = serializer.serialize(configAction);
        assertThat(serializedConfigAction.getNumber()).isEqualTo(expectedConfigAction.getNumber());
    }

    @ParameterizedTest
    @MethodSource("provideConfigActionEnum")
    void shouldDeserializeConfigActionEnum(
            ConfigActionEnum expectedConfigAction,
            ContextOuterClass.ConfigActionEnum serializedConfigAction) {
        final var configAction = serializer.deserialize(serializedConfigAction);
        assertThat(configAction).isEqualTo(expectedConfigAction);
    }

    @Test
    void shouldSerializeConfigRule() {
        final var expectedConfigRule =
                ContextOuterClass.ConfigRule.newBuilder()
                        .setAction(ContextOuterClass.ConfigActionEnum.CONFIGACTION_SET)
                        .setResourceKey("resourceKey")
                        .setResourceValue("resourceValue")
                        .build();

        final var configRule = new ConfigRule(ConfigActionEnum.SET, "resourceKey", "resourceValue");
        final var serializedConfigRule = serializer.serialize(configRule);

        assertThat(serializedConfigRule).usingRecursiveComparison().isEqualTo(expectedConfigRule);
    }

    @Test
    void shouldDeserializeConfigRule() {
        final var expectedConfigRule =
                new ConfigRule(ConfigActionEnum.DELETE, "resourceKey", "resourceValue");

        final var serializedConfigRule =
                ContextOuterClass.ConfigRule.newBuilder()
                        .setAction(ContextOuterClass.ConfigActionEnum.CONFIGACTION_DELETE)
                        .setResourceKey("resourceKey")
                        .setResourceValue("resourceValue")
                        .build();
        final var configRule = serializer.deserialize(serializedConfigRule);

        assertThat(configRule).usingRecursiveComparison().isEqualTo(expectedConfigRule);
    }

    @Test
    void shouldSerializeDeviceConfig() {
        final var expectedConfigRuleA =
                ContextOuterClass.ConfigRule.newBuilder()
                        .setAction(ContextOuterClass.ConfigActionEnum.CONFIGACTION_SET)
                        .setResourceKey("resourceKeyA")
                        .setResourceValue("resourceValueA")
                        .build();
        final var expectedConfigRuleB =
                ContextOuterClass.ConfigRule.newBuilder()
                        .setAction(ContextOuterClass.ConfigActionEnum.CONFIGACTION_DELETE)
                        .setResourceKey("resourceKeyB")
                        .setResourceValue("resourceValueB")
                        .build();
        final var expectedDeviceConfig =
                ContextOuterClass.DeviceConfig.newBuilder()
                        .addAllConfigRules(List.of(expectedConfigRuleA, expectedConfigRuleB))
                        .build();

        final var configRuleA = new ConfigRule(ConfigActionEnum.SET, "resourceKeyA", "resourceValueA");
        final var configRuleB =
                new ConfigRule(ConfigActionEnum.DELETE, "resourceKeyB", "resourceValueB");
        final var deviceConfig = new DeviceConfig(List.of(configRuleA, configRuleB));
        final var serializedDeviceConfig = serializer.serialize(deviceConfig);

        assertThat(serializedDeviceConfig).usingRecursiveComparison().isEqualTo(expectedDeviceConfig);
    }

    @Test
    void shouldDeserializeDeviceConfig() {
        final var expectedConfigRuleA =
                new ConfigRule(ConfigActionEnum.SET, "resourceKeyA", "resourceValueA");
        final var expectedConfigRuleB =
                new ConfigRule(ConfigActionEnum.DELETE, "resourceKeyB", "resourceValueB");
        final var expectedDeviceConfig =
                new DeviceConfig(List.of(expectedConfigRuleA, expectedConfigRuleB));

        final var configRuleA =
                ContextOuterClass.ConfigRule.newBuilder()
                        .setAction(ContextOuterClass.ConfigActionEnum.CONFIGACTION_SET)
                        .setResourceKey("resourceKeyA")
                        .setResourceValue("resourceValueA")
                        .build();
        final var configRuleB =
                ContextOuterClass.ConfigRule.newBuilder()
                        .setAction(ContextOuterClass.ConfigActionEnum.CONFIGACTION_DELETE)
                        .setResourceKey("resourceKeyB")
                        .setResourceValue("resourceValueB")
                        .build();
        final var serializedDeviceConfig =
                ContextOuterClass.DeviceConfig.newBuilder()
                        .addAllConfigRules(List.of(configRuleA, configRuleB))
                        .build();
        final var deviceConfig = serializer.deserialize(serializedDeviceConfig);

        assertThat(deviceConfig).usingRecursiveComparison().isEqualTo(expectedDeviceConfig);
    }

    private static Stream<Arguments> provideOperationalStatusEnum() {
        return Stream.of(
                Arguments.of(
                        DeviceOperationalStatus.ENABLED,
                        DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_ENABLED),
                Arguments.of(
                        DeviceOperationalStatus.DISABLED,
                        DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_DISABLED),
                Arguments.of(
                        DeviceOperationalStatus.UNDEFINED,
                        DeviceOperationalStatusEnum.DEVICEOPERATIONALSTATUS_UNDEFINED));
    }

    @ParameterizedTest
    @MethodSource("provideOperationalStatusEnum")
    void shouldSerializeOperationalStatusEnum(
            DeviceOperationalStatus opStatus,
            ContextOuterClass.DeviceOperationalStatusEnum expectedOpStatus) {
        final var serializedOpStatus = serializer.serialize(opStatus);
        assertThat(serializedOpStatus.getNumber()).isEqualTo(expectedOpStatus.getNumber());
    }

    @ParameterizedTest
    @MethodSource("provideOperationalStatusEnum")
    void shouldDeserializeOperationalStatusEnum(
            DeviceOperationalStatus expectedOpStatus,
            ContextOuterClass.DeviceOperationalStatusEnum serializedOpStatus) {
        final var operationalStatus = serializer.deserialize(serializedOpStatus);
        assertThat(operationalStatus).isEqualTo(expectedOpStatus);
    }

    @Test
    void shouldSerializeDevice() {
        final var expectedConfigRule =
                ContextOuterClass.ConfigRule.newBuilder()
                        .setAction(ContextOuterClass.ConfigActionEnum.CONFIGACTION_SET)
                        .setResourceKey("resourceKey")
                        .setResourceValue("resourceValue")
                        .build();
        final var expectedDeviceConfig =
                ContextOuterClass.DeviceConfig.newBuilder().addConfigRules(expectedConfigRule).build();
        final var deviceBuilder = ContextOuterClass.Device.newBuilder();
        deviceBuilder.setDeviceId(serializer.serializeDeviceId("deviceId"));
        deviceBuilder.setDeviceType("deviceType");
        deviceBuilder.setDeviceConfig(expectedDeviceConfig);
        deviceBuilder.setDeviceOperationalStatus(serializer.serialize(DeviceOperationalStatus.ENABLED));
        final var expectedDevice = deviceBuilder.build();

        final var deviceConfig =
                new DeviceConfig(
                        List.of(new ConfigRule(ConfigActionEnum.SET, "resourceKey", "resourceValue")));
        final var device =
                new Device("deviceId", "deviceType", deviceConfig, DeviceOperationalStatus.ENABLED);
        final var serializedDevice = serializer.serialize(device);

        assertThat(serializedDevice).usingRecursiveComparison().isEqualTo(expectedDevice);
    }

    @Test
    void shouldDeserializeDevice() {
        final var expectedConfig =
                new DeviceConfig(
                        List.of(new ConfigRule(ConfigActionEnum.DELETE, "resourceKey", "resourceValue")));
        final var expectedDevice =
                new Device("deviceId", "deviceType", expectedConfig, DeviceOperationalStatus.ENABLED);

        final var configRule =
                ContextOuterClass.ConfigRule.newBuilder()
                        .setAction(ContextOuterClass.ConfigActionEnum.CONFIGACTION_DELETE)
                        .setResourceKey("resourceKey")
                        .setResourceValue("resourceValue")
                        .build();
        final var deviceConfig =
                ContextOuterClass.DeviceConfig.newBuilder().addConfigRules(configRule).build();
        final var deviceBuilder = ContextOuterClass.Device.newBuilder();
        deviceBuilder.setDeviceId(serializer.serializeDeviceId("deviceId"));
        deviceBuilder.setDeviceType("deviceType");
        deviceBuilder.setDeviceConfig(deviceConfig);
        deviceBuilder.setDeviceOperationalStatus(serializer.serialize(DeviceOperationalStatus.ENABLED));
        final var serializedDevice = deviceBuilder.build();
        final var device = serializer.deserialize(serializedDevice);

        assertThat(device).usingRecursiveComparison().isEqualTo(expectedDevice);
    }

    @Test
    void shouldSerializeUuid() {
        final var expectedUuid = "uuid";

        final var serializeUuid = serializer.serializeUuid("uuid");

        assertThat(serializeUuid.getUuid()).isEqualTo(expectedUuid);
    }

    @Test
    void shouldDeserializeUuid() {
        final var expectedUuid = "uuid";

        final var uuid = serializer.deserialize(Uuid.newBuilder().setUuid("uuid").build());

        assertThat(uuid).isEqualTo(expectedUuid);
    }
}
