package eu.teraflow.automation;

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

import automation.Automation;
import automation.Automation.DeviceRole;
import automation.Automation.DeviceRoleId;
import automation.AutomationService;
import context.ContextOuterClass;
import context.ContextOuterClass.Uuid;
import eu.teraflow.automation.context.ContextGateway;
import eu.teraflow.automation.device.Device;
import eu.teraflow.automation.device.DeviceGateway;
import eu.teraflow.automation.device.model.*;
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.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
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;

    @InjectMock DeviceGateway deviceGateway;
    @InjectMock ContextGateway contextGateway;

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

        final var serializedUuid =
                Uuid.newBuilder()
                        .setUuid(UUID.fromString("0f14d0ab-9608-7862-a9e4-5ed26688389b").toString())
                        .build();
        final var deviceRoleId = DeviceRoleId.newBuilder().setDevRoleId(serializedUuid).build();
        final var deviceRole = DeviceRole.newBuilder().setDevRoleId(deviceRoleId).build();

        eu.teraflow.automation.device.model.Uuid uuid =
                new eu.teraflow.automation.device.model.Uuid(deviceRoleId.getDevId().toString());
        DeviceId deviceId = new DeviceId(uuid);
        DeviceType deviceType = new DeviceType("ztp");

        List<ConfigRule> configRuleList = new ArrayList<>();

        ConfigRule configRule1 =
                new ConfigRule(ConfigActionEnum.UNDEFINED, "001", "initial-configuration");
        configRuleList.add(configRule1);

        DeviceConfig deviceConfig = new DeviceConfig(configRuleList);
        //        Uni<DeviceConfig> deviceConfigUni = Uni.createFrom().item(deviceConfig);
        Uni<DeviceId> expectedDeviceId = Uni.createFrom().item(deviceId);

        DeviceConfig expectedDeviceConfig = new DeviceConfig(configRuleList);
        Uni<DeviceConfig> expectedDeviceConfigUni = Uni.createFrom().item(expectedDeviceConfig);

        Device device =
                new Device(deviceId, deviceType, deviceConfig, DeviceOperationalStatus.DISABLED);
        Uni<Device> deviceUni = Uni.createFrom().item(device);

        Mockito.when(contextGateway.getDevice(Mockito.any())).thenReturn(deviceUni);
        Mockito.when(deviceGateway.getInitialConfiguration(Mockito.any()))
                .thenReturn(expectedDeviceConfigUni);
        Mockito.when(deviceGateway.configureDevice(Mockito.any())).thenReturn(expectedDeviceId);

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

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

        final var uuid =
                Uuid.newBuilder()
                        .setUuid(UUID.fromString("0f14d0ab-9605-7862-a9e4-5ed26688389b").toString())
                        .build();
        final var deviceRoleId = DeviceRoleId.newBuilder().setDevRoleId(uuid).build();
        final var deviceRole = DeviceRole.newBuilder().setDevRoleId(deviceRoleId).build();

        client
                .ztpUpdate(deviceRole)
                .subscribe()
                .with(
                        deviceRoleState -> {
                            LOGGER.infof("Received response %s", deviceRoleState);
                            message.complete(deviceRoleState.getDevRoleId().toString());
                        });
        assertThat(message.get(5, TimeUnit.SECONDS)).contains("0f14d0ab-9605-7862-a9e4-5ed26688389b");
    }

    @Test
    void shouldGetDeviceRole() throws ExecutionException, InterruptedException, TimeoutException {

        CompletableFuture<String> message = new CompletableFuture<>();

        final var uuid =
                Uuid.newBuilder()
                        .setUuid(UUID.fromString("0f14d0ab-9605-4a62-a5c4-5ed26688389b").toString())
                        .build();
        final var deviceRoleId = DeviceRoleId.newBuilder().setDevRoleId(uuid).build();

        client
                .ztpGetDeviceRole(deviceRoleId)
                .subscribe()
                .with(
                        deviceRole -> {
                            LOGGER.infof("Received response %s", deviceRole);
                            message.complete(deviceRole.getDevRoleId().toString());
                        });
        assertThat(message.get(5, TimeUnit.SECONDS)).contains("0f14d0ab-9605-4a62-a5c4-5ed26688389b");
    }

    @Test
    void shouldGetAllDeviceRolesByDeviceId()
            throws ExecutionException, InterruptedException, TimeoutException {

        CompletableFuture<String> message = new CompletableFuture<>();

        final var uuid =
                Uuid.newBuilder()
                        .setUuid(UUID.fromString("1b14d0ab-9605-4a62-a9e4-5ed26688389b").toString())
                        .build();
        final var deviceId = ContextOuterClass.DeviceId.newBuilder().setDeviceUuid(uuid).build();

        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 =
                Uuid.newBuilder()
                        .setUuid(UUID.fromString("0c15d0ab-9605-4a62-a5c4-5ed26688389b").toString())
                        .build();
        final var deviceRoleId = DeviceRoleId.newBuilder().setDevRoleId(uuid).build();
        final var deviceRole = 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("0c15d0ab-9605-4a62-a5c4-5ed26688389b");
    }

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