/*
 * 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.AssertionsForClassTypes.assertThat;

import automation.Automation;
import context.ContextOuterClass;
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 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 javax.inject.Inject;
import org.assertj.core.api.Assertions;
import org.jboss.logging.Logger;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

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

    @Inject AutomationService automationService;

    @InjectMock DeviceGateway deviceGateway;
    @InjectMock ContextGateway contextGateway;

    @Test
    void shouldConfigureDevice() {

        final var uuidForDeviceRoleId =
                ContextOuterClass.Uuid.newBuilder()
                        .setUuid(UUID.fromString("0f14d0ab-9608-7862-a9e4-5ed26688389b").toString())
                        .build();

        final var uuidForDeviceId =
                ContextOuterClass.Uuid.newBuilder()
                        .setUuid(UUID.fromString("9f14d0ab-9608-7862-a9e4-5ed26688389c").toString())
                        .build();

        final var outDeviceId =
                ContextOuterClass.DeviceId.newBuilder().setDeviceUuid(uuidForDeviceId).build();

        final var outDeviceRoleId =
                Automation.DeviceRoleId.newBuilder()
                        .setDevRoleId(uuidForDeviceRoleId)
                        .setDevId(outDeviceId)
                        .build();

        String deviceId = outDeviceRoleId.getDevRoleId().toString();
        String deviceType = "cisco";

        ConfigRule configRule1 = new ConfigRule(ConfigActionEnum.UNDEFINED, "1", "1");
        ConfigRule configRule2 = new ConfigRule(ConfigActionEnum.SET, "2", "2");
        List<ConfigRule> configRuleList = new ArrayList<>();
        configRuleList.add(configRule1);
        configRuleList.add(configRule2);

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

        Device device = new Device(deviceId, deviceType, 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);

        final var currentDevice = automationService.addDevice(deviceId);

        Assertions.assertThat(currentDevice).isNotNull();
        currentDevice
                .subscribe()
                .with(
                        deviceConfig -> {
                            LOGGER.infof("Received response %s", deviceConfig);

                            assertThat(deviceConfig.getDeviceOperationalStatus().toString())
                                    .isEqualTo(device.getDeviceOperationalStatus().toString());

                            assertThat(deviceConfig.getDeviceConfig().toString()).isNotNull();

                            final var rulesList = deviceConfig.getDeviceConfig().getConfigRules();

                            for (int i = 0; i < rulesList.size(); i++) {
                                assertThat(rulesList.get(i).getResourceKey()).isEqualTo(String.valueOf(i + 1));
                                assertThat(rulesList.get(i).getResourceValue()).isEqualTo(String.valueOf(i + 1));
                            }
                            assertThat(deviceConfig.getDeviceType()).isEqualTo("cisco");
                            assertThat(deviceConfig.getDeviceId()).isEqualTo(deviceId);
                        });
    }

    @Test
    void shouldNotConfigureDevice() {

        final var uuidForDeviceRoleId =
                ContextOuterClass.Uuid.newBuilder()
                        .setUuid(UUID.fromString("2f14d0ab-9608-7862-a9e4-5ed26688389f").toString())
                        .build();

        final var uuidForDeviceId =
                ContextOuterClass.Uuid.newBuilder()
                        .setUuid(UUID.fromString("3f14d0ab-9608-7862-a9e4-5ed26688389d").toString())
                        .build();

        final var outDeviceId =
                ContextOuterClass.DeviceId.newBuilder().setDeviceUuid(uuidForDeviceId).build();

        final var outDeviceRoleId =
                Automation.DeviceRoleId.newBuilder()
                        .setDevRoleId(uuidForDeviceRoleId)
                        .setDevId(outDeviceId)
                        .build();

        String deviceId = outDeviceRoleId.getDevId().toString();
        String deviceType = "ztp";

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

        ConfigRule expectedConfigRule =
                new ConfigRule(ConfigActionEnum.UNDEFINED, "001", "already-configured");
        configRuleList.add(expectedConfigRule);

        DeviceConfig expectedDeviceConfig = new DeviceConfig(configRuleList);

        Device device =
                new Device(deviceId, deviceType, expectedDeviceConfig, DeviceOperationalStatus.ENABLED);

        Uni<Device> deviceUni = Uni.createFrom().item(device);

        Mockito.when(contextGateway.getDevice(Mockito.any())).thenReturn(deviceUni);

        final var currentDevice = automationService.addDevice(deviceId);

        Assertions.assertThat(currentDevice).isNotNull();

        currentDevice
                .subscribe()
                .with(
                        deviceConfig -> {
                            LOGGER.infof("Received response %s", deviceConfig);

                            assertThat(deviceConfig.getDeviceOperationalStatus().toString())
                                    .isEqualTo(device.getDeviceOperationalStatus().toString());

                            assertThat(deviceConfig.getDeviceConfig().toString()).isNotNull();

                            final var rulesList = deviceConfig.getDeviceConfig().getConfigRules();

                            for (ConfigRule configRule : rulesList) {
                                assertThat(configRule.getResourceKey())
                                        .isEqualTo(expectedConfigRule.getResourceKey());
                                assertThat(configRule.getResourceValue())
                                        .isEqualTo(expectedConfigRule.getResourceValue());
                            }
                            assertThat(deviceConfig.getDeviceType()).isEqualTo("ztp");
                            assertThat(deviceConfig.getDeviceId()).isEqualTo(deviceId);
                        });
    }
}
