/*
 * 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 automation.AutomationService;
import context.ContextOuterClass;
import eu.teraflow.automation.model.DeviceRole;
import eu.teraflow.automation.model.DeviceRoleId;
import eu.teraflow.automation.context.ContextGateway;
import eu.teraflow.automation.device.DeviceGateway;
import eu.teraflow.automation.device.model.*;
import eu.teraflow.automation.device.model.Device;
import eu.teraflow.automation.model.DeviceRoleType;
import io.quarkus.grpc.GrpcClient;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectMock;
import io.smallrye.mutiny.Uni;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.inject.Inject;
import org.jboss.logging.Logger;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

@QuarkusTest
class AutomationServiceTest {
    private static final Logger LOGGER = Logger.getLogger(AutomationServiceTest.class);

    @GrpcClient AutomationService client;
    private final Serializer serializer;

    @InjectMock DeviceGateway deviceGateway;
    @InjectMock ContextGateway contextGateway;

    @Inject
    AutomationServiceTest(Serializer serializer) {
        this.serializer = serializer;
    }

    @Test
    void shouldAddDeviceRole() throws ExecutionException, InterruptedException, TimeoutException {
        final var message = new CompletableFuture<>();
        final var DEVICE_ID = "0f14d0ab-9608-7862-a9e4-5ed26688389b";
        final var DEVICE_ROLE_ID = "0f14d0ab-9608-7862-a9e4-5ed26688389a";
        final var DEVICE_TYPE = "ztp";

        final var emptyDeviceConfig = new DeviceConfig(List.of());
        final var disabledDevice =
                new Device(DEVICE_ID, DEVICE_TYPE, emptyDeviceConfig, DeviceOperationalStatus.DISABLED);
        Mockito.when(contextGateway.getDevice(Mockito.any()))
                .thenReturn(Uni.createFrom().item(disabledDevice));

        final var configRule = new ConfigRule(ConfigActionEnum.SET, "001", "initial-configuration");
        final var initialDeviceConfig = new DeviceConfig(List.of(configRule));
        Mockito.when(deviceGateway.getInitialConfiguration(Mockito.any()))
                .thenReturn(Uni.createFrom().item(initialDeviceConfig));

        Mockito.when(deviceGateway.configureDevice(Mockito.any()))
                .thenReturn(Uni.createFrom().item(DEVICE_ID));

        final var deviceRoleId = new DeviceRoleId(DEVICE_ROLE_ID, DEVICE_ID);
        final var deviceRoleType = DeviceRoleType.DEV_OPS;
        final var deviceRole = new DeviceRole(deviceRoleId, deviceRoleType);
        final var serializedDeviceRole = serializer.serialize(deviceRole);

        client
                .ztpAdd(serializedDeviceRole)
                .subscribe()
                .with(
                        deviceRoleState -> {
                            LOGGER.infof("Received %s", deviceRoleState);
                            final var devRoleId = deviceRoleState.getDevRoleId();

                            final var deviceRoleIdUuid = serializer.deserialize(devRoleId);

                            assertThat(deviceRoleIdUuid.getId()).isEqualTo(DEVICE_ROLE_ID);

                            final var deviceId = serializer.deserialize(devRoleId.getDevId());
                            assertThat(deviceId).isEqualTo(DEVICE_ID);

                            final var devRoleUuid = serializer.deserialize(devRoleId.getDevRoleId());
                            message.complete(devRoleUuid);
                        });
        assertThat(message.get(5, TimeUnit.SECONDS)).isEqualTo(DEVICE_ROLE_ID);
    }

    @Test
    void shouldUpdateDeviceRole() throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> message = new CompletableFuture<>();
        final var DEVICE_ID = "0f14d0ab-9608-7862-a9e4-5ed26688389b";
        final var DEVICE_ROLE_ID = "0f14d0ab-9608-7862-a9e4-5ed26688389a";

        final var deviceRoleId = new DeviceRoleId(DEVICE_ROLE_ID, DEVICE_ID);
        final var deviceRoleType = DeviceRoleType.DEV_CONF;
        final var deviceRole = new DeviceRole(deviceRoleId, deviceRoleType);
        final var serializedDeviceRole = serializer.serialize(deviceRole);

        client
                .ztpUpdate(serializedDeviceRole)
                .subscribe()
                .with(
                        deviceRoleState -> {
                            LOGGER.infof("Received response %s", deviceRoleState);
                            message.complete(deviceRoleState.getDevRoleId().toString());
                        });
        assertThat(message.get(5, TimeUnit.SECONDS)).contains(DEVICE_ID);
    }

    @Test
    void shouldGetDeviceRole() throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> message = new CompletableFuture<>();
        final var DEVICE_ID = "0f14d0ab-9608-7862-a9e4-5ed26688389b";
        final var DEVICE_ROLE_ID = "0f14d0ab-9608-7862-a9e4-5ed26688389a";

        final var deviceRoleId = new DeviceRoleId(DEVICE_ROLE_ID, DEVICE_ID);
        final var serializeDeviceRoleId = serializer.serialize(deviceRoleId);

        client
                .ztpGetDeviceRole(serializeDeviceRoleId)
                .subscribe()
                .with(
                        deviceRole -> {
                            LOGGER.infof("Received response %s", deviceRole);
                            assertThat(deviceRole.getDevRoleId().getDevId().getDeviceUuid().getUuid())
                                    .isEqualTo(DEVICE_ID);
                            message.complete(deviceRole.getDevRoleId().toString());
                        });
        assertThat(message.get(5, TimeUnit.SECONDS)).contains(DEVICE_ROLE_ID);
    }

    @Test
    void shouldGetAllDeviceRolesByDeviceId()
            throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> message = new CompletableFuture<>();

        final var deviceId = serializer.serializeDeviceId("0f14d0ab-9605-7862-a9e4-5ed26688389b");

        client
                .ztpGetDeviceRolesByDeviceId(deviceId)
                .subscribe()
                .with(
                        deviceRoleList -> {
                            LOGGER.infof("Received response %s", deviceRoleList);
                            message.complete(deviceRoleList.toString());
                        });
        assertThat(message.get(5, TimeUnit.SECONDS)).isEmpty();
    }

    @Test
    void shouldDeleteDeviceRole() throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> message = new CompletableFuture<>();
        final var UUID_VALUE = "0f14d0ab-9605-7862-a9e4-5ed26688389b";

        final var uuid = serializer.serializeUuid(UUID_VALUE);
        final var deviceRoleId = Automation.DeviceRoleId.newBuilder().setDevRoleId(uuid).build();
        final var deviceRole = Automation.DeviceRole.newBuilder().setDevRoleId(deviceRoleId).build();

        client
                .ztpDelete(deviceRole)
                .subscribe()
                .with(
                        deviceRoleState -> {
                            LOGGER.infof("Received response %s", deviceRoleState);
                            message.complete(deviceRoleState.getDevRoleId().toString());
                        });
        assertThat(message.get(5, TimeUnit.SECONDS)).contains(UUID_VALUE);
    }

    @Test
    void shouldDeleteAllDevicesRolesByDeviceId()
            throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<String> message = new CompletableFuture<>();
        final var empty = Automation.Empty.newBuilder().build();

        client
                .ztpDeleteAll(empty)
                .subscribe()
                .with(
                        deletionResult -> {
                            LOGGER.infof("Received response %s", deletionResult);
                            message.complete(deletionResult.toString());
                        });
        assertThat(message.get(5, TimeUnit.SECONDS)).isEmpty();
    }
}
