package com.priusis.client.extensions.http;

import com.fasterxml.jackson.databind.JsonNode;
import com.priusis.client.data.kv.KvEntry;
import com.priusis.client.extensions.ExtensionUpdate;
import com.priusis.client.extensions.http.conf.HttpConfiguration;
import com.priusis.client.service.MqttDeliveryFuture;
import com.priusis.client.service.MqttRpcDataMessage;
import com.priusis.client.service.conf.PcExtensionConfiguration;
import com.priusis.client.service.core.MqttService;
import com.priusis.client.service.data.DeviceData;
import com.priusis.client.util.ConfigurationTools;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import static com.priusis.client.util.JsonTools.fromString;
import static com.priusis.client.util.JsonTools.getKvEntries;

@Slf4j
public class DefaultHttpService extends ExtensionUpdate implements HttpService {

    private static final int OPERATION_TIMEOUT_IN_SEC = 10;

    private final MqttService mqttService;
    private PcExtensionConfiguration currentConfiguration;
    private HttpConfiguration configuration;

    public DefaultHttpService(MqttService mqttService) {
        this.mqttService = mqttService;
    }

    @Override
    public PcExtensionConfiguration getCurrentConfiguration() {
        return currentConfiguration;
    }

    @Override
    public void init(PcExtensionConfiguration configurationNode) throws IOException {
        currentConfiguration = configurationNode;
        try {
            configuration = ConfigurationTools.readFileConfiguration(configurationNode.getExtensionConfiguration(), HttpConfiguration.class);
        } catch (IOException e) {
            log.error("[{}] Http service configuration failed!", mqttService.getTenantLabel(), e);
            mqttService.onConfigurationError(e, currentConfiguration);
            throw e;
        }
    }

    @Override
    public void destroy() throws Exception {
    }

    @Override
    public void processRequest(String token, String body) throws Exception {
        log.trace("[{}] Processing request body [{}] for token [{}]", mqttService.getTenantLabel(), body, token);
        if (configuration != null) {
            if (StringUtils.isEmpty(configuration.getToken()) || configuration.getToken().equals(token)) {
                processBody(body);
            } else {
                log.error("[{}] Request token [{}] doesn't match configuration token!", mqttService.getTenantLabel(), token);
                throw new SecurityException("Request token [" + token + "] doesn't match configuration token!");
            }
        }
    }

    @Override
    public void processEventRequest(String token, String body) throws Exception {
        log.trace("[{}] Processing request body [{}] for token [{}]", mqttService.getTenantLabel(), body, token);
        if (configuration != null) {
            if (StringUtils.isEmpty(configuration.getToken()) || configuration.getToken().equals(token)) {
                processEventBody(body);
            } else {
                log.error("[{}] Request token [{}] doesn't match configuration token!", mqttService.getTenantLabel(), token);
                throw new SecurityException("Request token [" + token + "] doesn't match configuration token!");
            }
        }
    }

    @Override
    public File flushRpcDataToFile(MqttRpcDataMessage mqttRpcDataMessage) throws IOException {
        return mqttService.flushRpcDataToFile(mqttRpcDataMessage);
    }

    @Override
    public MqttRpcDataMessage readFromFile(String method) throws IOException {
        return mqttService.readFromFile(method);
    }

    private void processEventBody(String body) throws Exception {
        /*JsonNode jsonObject = fromString(body);
        String methodName = jsonObject.get("methodName").asText();
        int requestId = jsonObject.get("requestId").asInt();
        String paramsStr = null;
        JsonNode params = jsonObject.get("params");
        if (null != params) {
            paramsStr = params.toString();
        }*/

        List<KvEntry> attrData = getKvEntries(fromString(body));
        DeviceData dd = new DeviceData(attrData);
        if (dd != null) {
            List<MqttDeliveryFuture> futures = new ArrayList<>();
            if (!dd.getAttributes().isEmpty()) {
                futures.add(mqttService.onDeviceEventUpdate(dd.getAttributes()));
            }
            for (Future future : futures) {
                waitWithTimeout(future);
            }
        } else {
            log.error("[{}] DeviceData is null. Body [{}] was not parsed successfully!", mqttService.getTenantLabel(), body);
            throw new IllegalArgumentException("Device Data is null. Body [" + body + "] was not parsed successfully!");
        }
    }

    private void processBody(String body) throws Exception {
        List<KvEntry> attrData = getKvEntries(fromString(body));
        DeviceData dd = new DeviceData(attrData);
        if (dd != null) {
            List<MqttDeliveryFuture> futures = new ArrayList<>();
            if (!dd.getAttributes().isEmpty()) {
                futures.add(mqttService.onDeviceAttributesUpdate(dd.getAttributes()));
            }
            for (Future future : futures) {
                waitWithTimeout(future);
            }
        } else {
            log.error("[{}] DeviceData is null. Body [{}] was not parsed successfully!", mqttService.getTenantLabel(), body);
            throw new IllegalArgumentException("Device Data is null. Body [" + body + "] was not parsed successfully!");
        }
    }

    private void waitWithTimeout(Future future) throws Exception {
        future.get(OPERATION_TIMEOUT_IN_SEC, TimeUnit.SECONDS);
    }
}
