package com.flowable.local.work.model;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;

@Component
public class FlowableJsonParser {

    private static final ObjectMapper objectMapper = new ObjectMapper();
    private static final JavaTimeModule javaTimeModule = new JavaTimeModule();

    public static final String BOOLEAN = "Boolean";
    public static final String STRING = "String";
    public static final String INTEGER = "Integer";
    public static final String DOUBLE = "Double";
    public static final String LONG = "Long";
    public static final String INSTANT = "Instant";
    public static final String JSON_OBJECT_NODE = "JsonObjectNode";
    public static final String JSON_ARRAY_NODE = "JsonArrayNode";
    public static final String MAP = "Map";
    public static final String ARRAY_LIST = "ArrayList";


    public FlowableJsonParser() {
        objectMapper.registerModule(javaTimeModule);
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    public Map<String, Object> parseMap(Object map) throws JsonProcessingException, ClassCastException {
        return (Map<String, Object>) parseObject(map);
    }

    public Object parseObject(Object object) throws JsonProcessingException, ClassCastException {
        if (object instanceof Map) {
            Map<String, Object> resultMap = new LinkedHashMap<>(Map.of());
            for (Map.Entry<String, Object> mapEntry : ((Map<String, Object>) object).entrySet()) {
                String key = mapEntry.getKey();
                resultMap.put(key, parseMapEntry(key, mapEntry.getValue()));
            }
            return resultMap;
        }
        if (object instanceof ArrayList) {
            ArrayList<Object> resultList = new ArrayList<>();
            for (Object arrayEntry : (ArrayList<Object>) object) {
                resultList.add(parseArrayEntry(arrayEntry));
            }
            return resultList;
        }
        return null;
    }

    private Object parseMapEntry(String key, Object value) throws JsonProcessingException {
        if (value instanceof Map) {
            Map<String, Object> valueMap = (Map<String, Object>) value;
            if (valueMap.containsKey("__TYPE") && valueMap.containsKey("__VALUE") && valueMap.size() == 2) {
                // if Map in Map, check if inner Map has explicit type (__TYPE and __VALUE)
                return convertObjectExplicit(valueMap.get("__VALUE"), (String) valueMap.get("__TYPE"));
            }
            if (Arrays.asList("__IN", "__OUT").contains(key)) {
                // convert __IN and __OUT maps as Map
                return parseObject(value);
            }
            // convert Map to JsonObjectNode (default in models)
            return convertObjectExplicit(value, JSON_OBJECT_NODE);
        }
        if (value instanceof ArrayList) {
            // convert ArrayList to JsonArrayNode (default in models)
            return convertObjectExplicit(value, JSON_ARRAY_NODE);
        }
        // take json supported type (Boolean, String, Integer, Double)
        return value;
    }

    private Object parseArrayEntry(Object value) throws JsonProcessingException {
        if (value instanceof Map) {
            // convert Map to JsonObjectNode (default in models)
            return convertObjectExplicit(value, JSON_OBJECT_NODE);
        }
        if (value instanceof ArrayList) {
            // convert ArrayList to JsonArrayNode (default in models)
            return convertObjectExplicit(value, JSON_ARRAY_NODE);
        }
        // take json supported type (Boolean, String, Integer, Double)
        return value;
    }

    private Object convertObjectExplicit(Object value, String explicitType) throws JsonProcessingException {
        switch (explicitType) {
            case BOOLEAN: return Boolean.parseBoolean(value.toString());
            case STRING: return value.toString();
            case INTEGER: return Integer.parseInt(value.toString());
            case DOUBLE: return Double.parseDouble(value.toString());
            case LONG: return Long.parseLong(value.toString());
            case INSTANT: return Instant.parse(value.toString());
            case MAP: case ARRAY_LIST: return parseObject(value);
            case JSON_OBJECT_NODE: case JSON_ARRAY_NODE: return objectMapper.convertValue(parseObject(value), JsonNode.class);
            default: return value;
        }
    }
}
