package eu.eudat.logic.mapper.prefilling;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.eudat.data.entities.Dataset;
import eu.eudat.data.entities.DatasetProfile;
import eu.eudat.elastic.entities.Tag;
import eu.eudat.logic.managers.DatasetManager;
import eu.eudat.logic.managers.DatasetProfileManager;
import eu.eudat.logic.managers.LicenseManager;
import eu.eudat.logic.proxy.config.entities.DefaultPrefillingMapping;
import eu.eudat.logic.proxy.config.entities.PrefillingFixedMapping;
import eu.eudat.logic.proxy.config.entities.PrefillingGet;
import eu.eudat.logic.proxy.config.entities.PrefillingMapping;
import eu.eudat.logic.proxy.config.exceptions.HugeResultSet;
import eu.eudat.logic.proxy.config.exceptions.NoURLFound;
import eu.eudat.logic.utilities.helpers.StreamDistinctBy;
import eu.eudat.logic.utilities.json.JsonSearcher;
import eu.eudat.models.data.components.commons.datafield.AutoCompleteData;
import eu.eudat.models.data.datasetprofile.DatasetProfileOverviewModel;
import eu.eudat.models.data.datasetprofile.RenderStyle;
import eu.eudat.models.data.datasetwizard.DatasetWizardModel;
import eu.eudat.models.data.externaldataset.ExternalAutocompleteFieldModel;
import eu.eudat.models.data.license.LicenseModel;
import org.json.JSONObject;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

public class PrefillingMapper {
    private static final ObjectMapper mapper = new ObjectMapper().configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

    public static DatasetWizardModel mapPrefilledEntityToDatasetWizard(Map<String, Object> prefilledEntity, PrefillingGet prefillingGet, String type,
                                                                       DatasetProfile profile, DatasetManager datasetManager, LicenseManager licenseManager) throws Exception {
        DatasetWizardModel datasetWizardModel = new DatasetWizardModel();
        datasetWizardModel.setProfile(new DatasetProfileOverviewModel().fromDataModel(profile));
        Dataset dataset = new Dataset();
        dataset.setProfile(profile);
        Map<String, Object> properties = new HashMap<>();
        JsonNode parentNode = mapper.readTree(mapper.writeValueAsString(datasetManager.getPagedProfile(datasetWizardModel, dataset)));
        for (DefaultPrefillingMapping prefillingMapping: prefillingGet.getMappings()) {
            List<String> sourceKeys = Arrays.asList(prefillingMapping.getSource().split("\\."));
            Object sourceValue = null;
            for (String sourceKey: sourceKeys) {
                if (sourceValue == null) {
                    sourceValue = prefilledEntity.get(sourceKey);
                } else if (sourceValue instanceof Map) {
                    sourceValue = ((Map)sourceValue).get(sourceKey);
                }
            }
            setValue(prefillingMapping, mapper.writeValueAsString(sourceValue), datasetWizardModel, parentNode, properties, type, licenseManager);
        }
        for (PrefillingFixedMapping fixedMapping: prefillingGet.getFixedMappings()) {
            setValue(fixedMapping, fixedMapping.getValue(), datasetWizardModel, parentNode, properties, type, licenseManager);
        }
        dataset.setProperties(mapper.writeValueAsString(properties));
        datasetWizardModel.setDatasetProfileDefinition(datasetManager.getPagedProfile(datasetWizardModel, dataset));
        return datasetWizardModel;
    }

    private static void setValue(PrefillingMapping prefillingMapping, String value, DatasetWizardModel datasetWizardModel, JsonNode parentNode, Map<String, Object> properties, String type, LicenseManager licenseManager) throws InvocationTargetException, IllegalAccessException, JsonProcessingException {
        String trimRegex = prefillingMapping.getTrimRegex() != null ? prefillingMapping.getTrimRegex() : "";
        if (!value.startsWith("\"") && !value.startsWith("[") && !value.equals("null")) {
            value = "\"" + value + "\"";
        }
        JsonNode valueNode = mapper.readTree(value);
        List<String> parsedValues = new ArrayList<>();
        if (valueNode.isArray() && (valueNode.get(0) != null && !valueNode.get(0).isTextual())) {
            if (prefillingMapping.getSubSource() == null || prefillingMapping.getSubSource().isEmpty()) {
                throw new IllegalArgumentException("Source value is an array but no subSource field have been set");
            }
            String parsedValue;
            for(int i = 0; i < valueNode.size(); i++){
                JsonNode jsonObj = valueNode.get(i);
                String subSource = jsonObj.get(prefillingMapping.getSubSource()).asText();
                parsedValue = subSource.replaceAll(trimRegex, "");
                parsedValues.add(parsedValue);
            }
            parsedValues = parsedValues.stream().distinct().collect(Collectors.toList());
        }
        String parsedValue = null;
        if (valueNode.isTextual()) {
            parsedValue = valueNode.textValue().replace(trimRegex, "");
        }else if (valueNode.isArray() && (valueNode.get(0) != null && valueNode.get(0).isTextual())) {
            List<String> values = new LinkedList<>();
            for (int i = 0; i < valueNode.size(); i++) {
                values.add(valueNode.get(i).textValue().replace(trimRegex, ""));
            }
            parsedValue = String.join(", ", values);
        }

        if (prefillingMapping.getTarget() != null) {
            try {
                String methodName = "set" + prefillingMapping.getTarget().substring(0, 1).toUpperCase(Locale.ROOT) + prefillingMapping.getTarget().substring(1);
                Method setterMethod = Arrays.stream(DatasetWizardModel.class.getDeclaredMethods())
                        .filter(method -> method.getName().equals(methodName)).collect(Collectors.toList()).get(0);
                Class<?>[] params = setterMethod.getParameterTypes();
                //GK: Tags Special logic
                if (parsedValue != null && !parsedValue.equals("null") && prefillingMapping.getTarget().equals("tags")) {
                    parsedValue = mapper.valueToTree(parseTags(parsedValue)).toString();
                } else {
                    parsedValue = mapper.valueToTree(parsedValue).toString();
                }
                setterMethod.invoke(datasetWizardModel, mapper.readValue(parsedValue, params[0]));
            }catch (InvocationTargetException | IllegalAccessException | JsonProcessingException e) {
                throw e;
            }
        } else {
            List<JsonNode> nodes = JsonSearcher.findNodes(parentNode, "rdaProperty", prefillingMapping.getMaDmpTarget());

            // zenodo prefilling customizations
            if(type.equals("zenodo")){
                if(prefillingMapping.getMaDmpTarget().equals("dataset.distribution.data_access")){
                    if(parsedValue != null && parsedValue.equals("open")){
                        List<JsonNode> issuedNodes = JsonSearcher.findNodes(parentNode, "rdaProperty", "dataset.issued");
                        if(!issuedNodes.isEmpty()){
                            String issuedIdNode = issuedNodes.get(0).get("id").asText();
                            String issuedValue = (String) properties.get(issuedIdNode);
                            List<JsonNode> licStartDateNodes = JsonSearcher.findNodes(parentNode, "rdaProperty", "dataset.distribution.license.start_date");
                            for (JsonNode licStartDateNode : licStartDateNodes) {
                                String licStartDateId = licStartDateNode.get(0) != null ? licStartDateNode.get(0).get("id").asText() : licStartDateNode.get("id").asText();
                                properties.put(licStartDateId, issuedValue);
                            }
                        }
                    }
                }

                if (prefillingMapping.getMaDmpTarget().equals("dataset.distribution.available_until") && parsedValue != null && !parsedValue.equals("null")) {
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd");
                    LocalDate date = LocalDate.parse(parsedValue, formatter);
                    date = date.plusYears(20);
                    parsedValue = date.toString();
                }
            }

            for (JsonNode node: nodes) {
                String id = node.isArray() ? node.get(0).get("id").asText() : node.get("id").asText();
                String renderStyle = node.isArray() ? node.get(0).get("viewStyle").get("renderStyle").asText() : node.get("viewStyle").get("renderStyle").asText();

                switch (RenderStyle.fromValue(renderStyle)) {
                    case COMBO_BOX:
                        if (parsedValues.isEmpty())
                            parsedValues.add(parsedValue);
                        if (!parsedValues.stream().allMatch(Objects::isNull)) {
                            properties.put(id, parseComboBoxValues(node, parsedValues));
                        }
                        break;
                    case TAGS:
                        properties.put(id, mapper.valueToTree(parseTags(parsedValue)).toString());
                        break;
                    case DATASET_IDENTIFIER:
                        JSONObject datasetID = new JSONObject();
                        datasetID.put("identifier", parsedValue);
                        if(type.equals("zenodo")){
                            datasetID.put("type", "doi");
                        }
                        properties.put(id, datasetID.toString());
                        break;
                    case LICENSES:
                        try {
                            List<LicenseModel> licenses = licenseManager.getLicenses("", "");
                            String finalParsedValue = parsedValue;
                            licenses = licenses.stream().filter(license -> license.getPid().equals(finalParsedValue)).collect(Collectors.toList());
                            boolean isMultiAutocomplete = node.isArray() ? node.get(0).get("data").get("multiAutoComplete").booleanValue() : node.get("data").get("multiAutoComplete").booleanValue();
                            if(isMultiAutocomplete){
                                List<String> lic = new ArrayList<>();
                                for(LicenseModel license: licenses){
                                    lic.add(mapper.writeValueAsString(license));
                                }
                                properties.put(id, mapper.writeValueAsString(lic));
                            } else {
                                properties.put(id, mapper.writeValueAsString(licenses.get(0)));
                            }
                        }
                        catch (NoURLFound | HugeResultSet e){
                            properties.put(id, parsedValue);
                        }
                        break;
                    default:
                        if (!parsedValues.isEmpty())
                            properties.put(id, String.join(", ", parsedValues));
                        else {
                            properties.put(id, parsedValue);
                        }
                        break;
                }
            }
        }
    }

    private static Object parseComboBoxValues(JsonNode node, List<String> parsedValues) throws JsonProcessingException {
        List<Object> normalizedValues = new ArrayList<>();
        boolean isMultiSelect;
        String type = node.isArray() ? node.get(0).get("data").get("type").asText() : node.get("data").get("type").asText();
        if(type.equals("autocomplete")) {
            JsonNode dataNode = node.isArray() ? node.get(0).get("data") : node.get("data");
            AutoCompleteData autoCompleteData = mapper.treeToValue(dataNode, AutoCompleteData.class);
            isMultiSelect = autoCompleteData.getMultiAutoComplete();
            for (String format : parsedValues) {
                List<ExternalAutocompleteFieldModel> result = DatasetProfileManager.getAutocomplete(autoCompleteData, format);
                result = result.stream().filter(StreamDistinctBy.distinctByKey(ExternalAutocompleteFieldModel::getId)).collect(Collectors.toList());
                if(!result.isEmpty()){
                    List<String> tempValues = new LinkedList<>();
                    for (ExternalAutocompleteFieldModel f : result) {
                        if (format.equals(f.getId()) || f.getLabel().toUpperCase(Locale.ROOT).contains(format.toUpperCase(Locale.ROOT)))
                            tempValues.add(mapper.valueToTree(f).toString());
                    }
                    if (isMultiSelect)
                        normalizedValues.addAll(tempValues);
                    else if (!tempValues.isEmpty())
                        normalizedValues.add(tempValues.get(0));
                }
            }
            return !normalizedValues.isEmpty() ? (isMultiSelect ? normalizedValues : normalizedValues.get(0)) : null;
        } else {
            JsonNode optionsNode = node.isArray() ? node.get(0).get("data").get("options") : node.get("data").get("options");
            isMultiSelect = node.isArray() ? node.get(0).get("data").get("multiList").booleanValue() : node.get("data").get("multiList").booleanValue();
            for (int i = 0; i < optionsNode.size(); i++) {
                String value = optionsNode.get(i).get("value").textValue();
                if (parsedValues.contains(value)) {
                    normalizedValues.add(value);
                }
            }
            List<String> normalizedStringValues = normalizedValues.stream().map(Object::toString).collect(Collectors.toList());

            return !normalizedValues.isEmpty() ? (isMultiSelect ? String.join(", ", normalizedStringValues) : normalizedValues.get(0)) : null;
        }
    }

    private static List<Tag> parseTags(String value) throws JsonProcessingException {
        if (value == null || value.isEmpty())
            return new LinkedList<>();
        String[] rawTags = value.split(", ");
        List<Tag> parsedTags = new LinkedList<>();
        for (String rawTag : rawTags) {
            parsedTags.add(new Tag(rawTag, rawTag));
        }
        return parsedTags;
    }
}
