/*
 * Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package eu.teraflow.automation;

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

import automation.Automation;
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 eu.teraflow.automation.model.DeviceRole;
import eu.teraflow.automation.model.DeviceRoleId;
import eu.teraflow.automation.model.DeviceRoleType;
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 expectedDeviceId = "expectedDeviceId";

        final var deviceIdUuid = serializer.serializeUuid(expectedDeviceId);
        final var deviceId = ContextOuterClass.DeviceId.newBuilder().setDeviceUuid(deviceIdUuid).build();

        final var serializedDeviceId = serializer.serializeDeviceId(expectedDeviceId);

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

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

        final var serializedDeviceIdUuid = serializer.serializeUuid("expectedDeviceId");
        final var serializedDeviceId = DeviceId.newBuilder().setDeviceUuid(serializedDeviceIdUuid).build();

        final var deviceId = serializer.deserialize(serializedDeviceId);

        assertThat(deviceId).isEqualTo(expectedDeviceId);
    }

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

        final var deviceRoleId = new DeviceRoleId(expectedDevRoleId, expectedDeviceId);
        final var serializedDeviceRoleIdUuid = serializer.serializeUuid(expectedDevRoleId);
        final var serializedDeviceRoleDeviceIdUuid = serializer.serializeUuid(expectedDeviceId);
        final var serializedDeviceRoleDeviceId = ContextOuterClass.DeviceId.newBuilder().setDeviceUuid(serializedDeviceRoleDeviceIdUuid).build();

        final var expectedDeviceRoleId =
                Automation.DeviceRoleId.newBuilder()
                        .setDevRoleId(serializedDeviceRoleIdUuid)
                        .setDevId(serializedDeviceRoleDeviceId)
                        .build();

        final var serializedDevRoleId =
                serializer.serialize(deviceRoleId);

        assertThat(serializedDevRoleId).usingRecursiveComparison().isEqualTo(expectedDeviceRoleId);
    }

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

        final var expectedDeviceRoleId = new DeviceRoleId(expectedDevRoleId, expectedDeviceId);

        final var serializedDeviceRoleId = serializer.serialize(expectedDeviceRoleId);
        final var deviceRoleId = serializer.deserialize(serializedDeviceRoleId);

        assertThat(deviceRoleId).usingRecursiveComparison().isEqualTo(expectedDeviceRoleId);
    }

    private static Stream<Arguments> provideDeviceRoleType() {
        return Stream.of(
                Arguments.of(DeviceRoleType.DEV_OPS, Automation.DeviceRoleType.DEV_OPS),
                Arguments.of(DeviceRoleType.DEV_CONF, Automation.DeviceRoleType.DEV_CONF),
                Arguments.of(DeviceRoleType.NONE, Automation.DeviceRoleType.NONE),
                Arguments.of(DeviceRoleType.PIPELINE_CONF, Automation.DeviceRoleType.PIPELINE_CONF));
    }

    @ParameterizedTest
    @MethodSource("provideDeviceRoleType")
    void shouldSerializeDeviceRoleType(
            DeviceRoleType deviceRoleType, Automation.DeviceRoleType expectedSerializedType) {
        final var serializedType = serializer.serialize(deviceRoleType);
        assertThat(serializedType.getNumber()).isEqualTo(expectedSerializedType.getNumber());
    }

    @ParameterizedTest
    @MethodSource("provideDeviceRoleType")
    void shouldDeserializeDeviceRoleType(
            DeviceRoleType expectedDeviceRoleType,
            Automation.DeviceRoleType serializedDeviceRoleTypeType) {

        final var deviceRoleType = serializer.deserialize(serializedDeviceRoleTypeType);

        assertThat(deviceRoleType).isEqualTo(expectedDeviceRoleType);
    }

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

        final var serializedDeviceRoleDevRoleIdUuid = serializer.serializeUuid(expectedDevRoleId);
        final var serializedDeviceRoleDeviceId = serializer.serializeDeviceId(expectedDeviceId);

        final var expectedDeviceRoleId = Automation.DeviceRoleId.newBuilder()
                .setDevRoleId(serializedDeviceRoleDevRoleIdUuid)
                .setDevId(serializedDeviceRoleDeviceId)
                .build();

        final var expectedDeviceRoleType = Automation.DeviceRoleType.PIPELINE_CONF;

        final var expectedDeviceRole = Automation.DeviceRole.newBuilder()
                .setDevRoleId(expectedDeviceRoleId)
                .setDevRoleType(expectedDeviceRoleType)
                .build();

        final var deviceRoleId = new DeviceRoleId(expectedDevRoleId,expectedDeviceId);
        final var deviceRoleType = DeviceRoleType.PIPELINE_CONF;

        final var deviceRole = new DeviceRole(deviceRoleId, deviceRoleType);
        final var serializedDeviceRole = serializer.serialize(deviceRole);

        assertThat(serializedDeviceRole).usingRecursiveComparison().isEqualTo(expectedDeviceRole);
    }

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

        final var expectedDeviceRoleId = new DeviceRoleId(expectedDevRoleId, expectedDeviceId);
        final var expectedDeviceRoleType = DeviceRoleType.NONE;

        final var expectedDeviceRole = new DeviceRole(expectedDeviceRoleId, expectedDeviceRoleType);

        final var serializedDeviceRoleId = serializer.serialize(expectedDeviceRoleId);
        final var serializedDeviceRoleType = serializer.serialize(expectedDeviceRoleType);

        final var serializedDeviceRole =
                Automation.DeviceRole.newBuilder()
                        .setDevRoleId(serializedDeviceRoleId)
                        .setDevRoleType(serializedDeviceRoleType)
                        .build();

        final var deviceRole = serializer.deserialize(serializedDeviceRole);

        assertThat(deviceRole).usingRecursiveComparison().isEqualTo(expectedDeviceRole);
    }

    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();

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

        deviceBuilder.setDeviceId(serializedDeviceId);
        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 serializedDeviceId = serializer.serializeDeviceId("deviceId");

        final var deviceBuilder = ContextOuterClass.Device.newBuilder();
        deviceBuilder.setDeviceId(serializedDeviceId);
        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);
    }
}
