package com.customer.work.model;

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.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Converts plain Java test data (maps, lists, scalars) into the variable types
 * used by Flowable models. Maps and lists become JsonNodes by default; an inner
 * map of the form {"__TYPE": "...", "__VALUE": ...} requests an explicit type.
 */
@Component
public class FlowableJsonParser {

    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";

    protected final ObjectMapper objectMapper = new ObjectMapper();

    public FlowableJsonParser() {
        // Enable ObjectMapper for handling Instant as string
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    }

    public Map<String, Object> parseMap(Object map) {
        Object parsed = parseObject(map);
        if (!(parsed instanceof Map)) {
            throw new IllegalArgumentException("Not a map: " + map);
        }
        @SuppressWarnings("unchecked")
        Map<String, Object> result = (Map<String, Object>) parsed;
        return result;
    }

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

    private Object parseMapEntry(String key, Object value) {
        if (value instanceof Map<?, ?> valueMap) {
            if (valueMap.size() == 2 && valueMap.containsKey("__TYPE") && valueMap.containsKey("__VALUE")) {
                // if Map in Map, check if inner Map has explicit type (__TYPE and __VALUE)
                return convertObjectExplicit(valueMap.get("__VALUE"), String.valueOf(valueMap.get("__TYPE")));
            }
            if (FlowableModelTestUtils.IN_PARAM.equals(key) || FlowableModelTestUtils.OUT_PARAM.equals(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 List) {
            // convert List 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) {
        if (value instanceof Map) {
            // convert Map to JsonObjectNode (default in models)
            return convertObjectExplicit(value, JSON_OBJECT_NODE);
        }
        if (value instanceof List) {
            // convert List 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) {
        return switch (explicitType) {
            case BOOLEAN -> Boolean.parseBoolean(value.toString());
            case STRING -> value.toString();
            case INTEGER -> Integer.parseInt(value.toString());
            case DOUBLE -> Double.parseDouble(value.toString());
            case LONG -> Long.parseLong(value.toString());
            case INSTANT -> Instant.parse(value.toString());
            case MAP, ARRAY_LIST -> parseObject(value);
            case JSON_OBJECT_NODE, JSON_ARRAY_NODE -> objectMapper.convertValue(parseObject(value), JsonNode.class);
            default -> value;
        };
    }
}
