package id.iptek.utms.agent.worker;

import com.google.gson.Gson;
import id.iptek.utms.agent.AppConfig;
import id.iptek.utms.agent.model.ApplicationSimple;
import id.iptek.utms.agent.model.Diagnostic;
import id.iptek.utms.agent.model.DiagnosticInfo;
import id.iptek.utms.agent.model.LocationInfo;
import id.iptek.utms.agent.queue.ConsumerMode;
import id.iptek.utms.agent.queue.DelayMessageQueue;
import id.iptek.utms.agent.queue.DiagnosticInfoQueueMessageHandler;
import id.iptek.utms.agent.util.Singleton;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.eclipse.paho.client.mqttv3.MqttMessage;

public class DiagnosticWorker extends Worker {

    @Override
    void init(String consumerName, AppConfig appConfig) throws Exception {
        super.init(consumerName, appConfig);
        
        DelayMessageQueue<Diagnostic> queue = (DelayMessageQueue<Diagnostic>) Singleton.getInstance(DiagnosticInfoQueueMessageHandler.NAME).getObject();
        ConsumerMode mode = ConsumerMode.valueOf(appConfig.get("mqtt.consumer."+consumerName+".consumermode", "SINGLE"));
        int batchCapacity = appConfig.getAsInt("mqtt.consumer."+consumerName+".batch.capacity", 10);
        queue.setCapacity(batchCapacity);
        queue.setMode(mode);
    }

    @Override
    public void messageArrived(String string, MqttMessage mm) throws Exception {
        try {
            String json = new String(mm.getPayload());
            this.logger.debug("<< " + json + " (" + mm.getId() + " - " + mm.getQos() + ")");
            Gson gson = new Gson();
            
            Map diagnosticReqMap = (Map) gson.fromJson(json, Map.class);
            if(!"DIAGNOSTIC".equals((String) diagnosticReqMap.get("req_type"))) {
                // skip message
                return;
            }
            
            Diagnostic diagnosticReq = mapDiagnosticData(diagnosticReqMap);
            // set id
            diagnosticReq.setId(UUID.randomUUID().toString());
            diagnosticReq.setCreateTime(new Date());
            
            DelayMessageQueue<Diagnostic> queue = (DelayMessageQueue<Diagnostic>) Singleton.getInstance(DiagnosticInfoQueueMessageHandler.NAME).getObject();
            boolean queued = queue.add(diagnosticReq);
            if(!queued) {
                logger.error("Diagnostic NOT queued: {}", queued);
            }
        } catch (Exception ex) {
            this.logger.error("Error saving diagnostic: {}", ex.getMessage(), ex);
        }
    }

    private Diagnostic mapDiagnosticData(Map map) {
        String reqId = (String) map.get("req_id");
        String terminalSN = (String) map.get("device_sn");
        Diagnostic diagnostic = new Diagnostic();
        diagnostic.setId(UUID.randomUUID().toString());
        diagnostic.setReqId(reqId);
        diagnostic.setDeviceSn(terminalSN);
        Map diagnosticInfoMap = (Map) map.get("diagnostic_info");
        boolean isNewDiagnostic = getBoolean(diagnosticInfoMap, "new_diagnostic");
        DiagnosticInfo diagnosticInfo = new DiagnosticInfo();
        diagnostic.setDiagnosticInfo(diagnosticInfo);
        diagnosticInfo.setBatteryPercentage(getInt(diagnosticInfoMap, "battery_percentage"));
        double temp = getDouble(diagnosticInfoMap, "battery_temp");
        if (isNewDiagnostic) {
            temp /= 10.0D;
        }
        diagnosticInfo.setBatteryTemp(temp);
        diagnosticInfo.setMeid((String) diagnosticInfoMap.get("meid"));
        diagnosticInfo.setTotalMemory((String) diagnosticInfoMap.get("total_memory"));
        diagnosticInfo.setAvailableMemory((String) diagnosticInfoMap.get("available_memory"));
        diagnosticInfo.setTotalFlashMemory((String) diagnosticInfoMap.get("total_flash_memory"));
        diagnosticInfo.setAvailableFlashMemory((String) diagnosticInfoMap.get("available_flash_memory"));
        diagnosticInfo.setTotalMobileData((String) diagnosticInfoMap.get("total_mobile_data"));
        diagnosticInfo.setCurrentBootTime(getInt(diagnosticInfoMap, "current_boot_time"));
        diagnosticInfo.setTotalBootTime(getInt(diagnosticInfoMap, "total_boot_time"));
        diagnosticInfo.setTotalLengthPrinted(getDouble(diagnosticInfoMap, "total_length_printed"));
        diagnosticInfo.setSwitchingTimes(getInt(diagnosticInfoMap, "switching_times"));
        diagnosticInfo.setSwipingCardTimes(getInt(diagnosticInfoMap, "swiping_card_times"));
        diagnosticInfo.setDipInsertingTimes(getInt(diagnosticInfoMap, "dip_inserting_times"));
        diagnosticInfo.setNfcCardReadingTimes(getInt(diagnosticInfoMap, "nfc_card_reading_times"));
        diagnosticInfo.setFrontCameraOpenTimes(getInt(diagnosticInfoMap, "front_camera_open_times"));
        diagnosticInfo.setRearCameraOpenTimes(getInt(diagnosticInfoMap, "rear_camera_open_times"));
        diagnosticInfo.setChargeTimes(getInt(diagnosticInfoMap, "charge_times"));
        List<Map<String, Object>> installedApps = (List<Map<String, Object>>) map.get("installed_apps");
        if (installedApps != null) {
            List<ApplicationSimple> applications = new ArrayList<>();
            installedApps.forEach(appMap -> {
                String appName = getString(appMap, "app_name");
                String packageName = getString(appMap, "package_name");
                String version = getString(appMap, "version");
                ApplicationSimple appSimple = new ApplicationSimple();
                appSimple.setId(UUID.randomUUID().toString());
                appSimple.setAppName(appName);
                appSimple.setPackageName(packageName);
                appSimple.setVersion(version);
                applications.add(appSimple);
            });
            diagnostic.setInstalledApps(applications);
            Gson gson = new Gson();
            String installedAppString = gson.toJson(applications);
            diagnosticInfo.setInstalledAppString(installedAppString);
        }
        Map locationInfoMap = (Map) map.get("location_info");
        LocationInfo locationInfo = new LocationInfo();
        diagnostic.setLocationInfo(locationInfo);
        locationInfo.setLat((Double) locationInfoMap.get("lat"));
        locationInfo.setLng((Double) locationInfoMap.get("lng"));
        return diagnostic;
    }

    private boolean getBoolean(Map map, String key) {
        Object val = map.get(key);
        if (val instanceof String) {
            String sVal = (String) val;
            return Boolean.parseBoolean(sVal.trim());
        }
        if (val instanceof Boolean) {
            return ((Boolean) val).booleanValue();
        }
        return false;
    }

    private double getDouble(Map map, String key) {
        Object val = map.get(key);
        if (val instanceof String) {
            String sVal = (String) val;
            return Double.parseDouble(sVal.trim());
        }
        if (val instanceof Double) {
            return ((Double) val).doubleValue();
        }
        if (val instanceof Integer) {
            return ((Double) val).doubleValue();
        }
        return 0.0D;
    }

    private int getInt(Map map, String key) {
        Object val = map.get(key);
        if (val instanceof String) {
            String sVal = (String) val;
            return Integer.parseInt(sVal.trim());
        }
        if (val instanceof Integer) {
            return ((Integer) val).intValue();
        }
        if (val instanceof Double) {
            return ((Double) val).intValue();
        }
        return 0;
    }

    private long getLong(Map map, String key) {
        Object val = map.get(key);
        if (val instanceof String) {
            String sVal = (String) val;
            if (sVal.contains("B")) {
                sVal = sVal.replace("B", "");
            }
            return Long.parseLong(sVal.trim());
        }
        if (val instanceof Long) {
            return ((Long) val).longValue();
        }
        if (val instanceof Double) {
            return ((Double) val).longValue();
        }
        return 0L;
    }

    private String getString(Map map, String key) {
        return (String) map.get(key);
    }
}
