package eu.teraflow.automation.device;

import context.ContextOuterClass;
import context.ContextOuterClass.DeviceConfig;
import context.ContextOuterClass.Uuid;
import device.DeviceService;
import eu.teraflow.automation.device.model.*;
import io.quarkus.grpc.GrpcClient;
import io.smallrye.mutiny.Uni;
import java.util.ArrayList;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;

@ApplicationScoped
public class DeviceGatewayImpl implements DeviceGateway {

    @GrpcClient DeviceService deviceDelegate;

    @Override
    public Uni<eu.teraflow.automation.device.model.DeviceConfig> getInitialConfiguration(
            DeviceId deviceId) {
        final var serializedId = getSerializedDeviceId(deviceId);
        final var serializedDeviceConfigUni = deviceDelegate.getInitialConfig(serializedId);

        return serializedDeviceConfigUni.onItem().transform(this::getDeviceConfig);
    }

    @Override
    public Uni<DeviceId> configureDevice(Device device) {
        final var deviceBuilder = ContextOuterClass.Device.newBuilder();

        final var serializedId = getSerializedDeviceId(device.getDeviceId());
        deviceBuilder.setDeviceId(serializedId);

        final var serializedConfigUni = getSerializedConfigUni(device.getDeviceConfig());
        final var serializedConfig = serializedConfigUni.await().indefinitely();
        deviceBuilder.setDeviceConfig(serializedConfig);

        final var serializedDevice = deviceBuilder.build();
        final var deviceIdUni = deviceDelegate.configureDevice(serializedDevice);

        return deviceIdUni.onItem().transform(this::getDeviceId);
    }

    private List<ContextOuterClass.ConfigRule> getSerializedConfigRule(
            List<ConfigRule> configRuleList) {

        List<ContextOuterClass.ConfigRule> serializedConfigRuleList = new ArrayList<>();
        for (ConfigRule item : configRuleList) {
            final var configActionEnum = getSerializedConfigActionEnum(item.getConfigActionEnum());
            final var resourceKey = item.getResourceKey();
            final var resourceValue = item.getResourceValue();
            final var serializedConfigRule =
                    ContextOuterClass.ConfigRule.newBuilder()
                            .setAction(configActionEnum)
                            .setResourceKey(resourceKey)
                            .setResourceValue(resourceValue)
                            .build();
            serializedConfigRuleList.add(serializedConfigRule);
        }
        return serializedConfigRuleList;
    }

    private ContextOuterClass.ConfigActionEnum getSerializedConfigActionEnum(
            ConfigActionEnum configActionEnum) {

        if (configActionEnum == ConfigActionEnum.SET) {
            return ContextOuterClass.ConfigActionEnum.CONFIGACTION_SET;
        } else if (configActionEnum == ConfigActionEnum.UNDEFINED) {
            return ContextOuterClass.ConfigActionEnum.CONFIGACTION_UNDEFINED;
        } else {
            return ContextOuterClass.ConfigActionEnum.CONFIGACTION_DELETE;
        }
    }

    private ConfigActionEnum getConfigActionEnum(
            ContextOuterClass.ConfigActionEnum configActionEnum) {

        if (configActionEnum == ContextOuterClass.ConfigActionEnum.CONFIGACTION_SET) {
            return ConfigActionEnum.SET;
        } else if (configActionEnum == ContextOuterClass.ConfigActionEnum.CONFIGACTION_UNDEFINED) {
            return ConfigActionEnum.UNDEFINED;
        } else {
            return ConfigActionEnum.DELETE;
        }
    }

    private ContextOuterClass.DeviceId getSerializedDeviceId(DeviceId deviceId) {
        final var deviceIdUuid = Uuid.newBuilder().setUuid(deviceId.toString()).build();
        return ContextOuterClass.DeviceId.newBuilder().setDeviceUuid(deviceIdUuid).build();
    }

    private DeviceId getDeviceId(ContextOuterClass.DeviceId deviceId) {
        eu.teraflow.automation.device.model.Uuid uuid =
                new eu.teraflow.automation.device.model.Uuid(deviceId.getDeviceUuid().getUuid());
        return new DeviceId(uuid);
    }

    private eu.teraflow.automation.device.model.DeviceConfig getDeviceConfig(
            ContextOuterClass.DeviceConfig deviceConfig) {

        final var serializedConfigRuleList = deviceConfig.getConfigRulesList();
        final var configRuleList = getConfigRuleList(serializedConfigRuleList);
        return new eu.teraflow.automation.device.model.DeviceConfig(configRuleList);
    }

    private List<ConfigRule> getConfigRuleList(
            List<ContextOuterClass.ConfigRule> serializedConfigRuleList) {

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

        for (ContextOuterClass.ConfigRule item : serializedConfigRuleList) {

            ConfigRule configRule =
                    new ConfigRule(
                            getConfigActionEnum(item.getAction()),
                            item.getResourceKey(),
                            item.getResourceValue());
            configRuleList.add(configRule);
        }
        return configRuleList;
    }

    private Uni<DeviceConfig> getSerializedConfigUni(
            eu.teraflow.automation.device.model.DeviceConfig deviceConfig) {

        final var serializedDeviceConfig = DeviceConfig.newBuilder();

        final var configRuleList = deviceConfig.getConfigRules();
        final var serializedConfigRuleList = getSerializedConfigRule(configRuleList);
        for (int i = 0; i < serializedConfigRuleList.size(); i++) {
            serializedDeviceConfig.setConfigRules(i, serializedConfigRuleList.get(i));
        }

        return Uni.createFrom().item(serializedDeviceConfig.build());
    }
}
