package eu.eudat.models.rda.mapper;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.TextNode;
import eu.eudat.logic.utilities.helpers.MyStringUtils;
import eu.eudat.models.rda.Metadatum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

public class MetadataRDAMapper {
	private static final Logger logger = LoggerFactory.getLogger(MetadataRDAMapper.class);

	public static List<Metadatum> toRDAList(List<JsonNode> nodes) {
		Map<String, String> rdaMap = new HashMap<>();
		List<Metadatum> rdas = new ArrayList<>();
		for (JsonNode node: nodes) {
			String rdaProperty = node.get("rdaProperty").asText();
			JsonNode rdaValue = node.get("value");

			for (PropertyName propertyName: PropertyName.values()) {
				if (rdaProperty.contains(propertyName.getName())) {
					switch (propertyName) {
						case METADATA_STANDARD_ID:
							if (rdaValue instanceof ArrayNode) {
								try {
									ObjectMapper mapper = new ObjectMapper();
									for (Iterator<JsonNode> it = rdaValue.elements(); it.hasNext(); ) {
										JsonNode data = null;
										data = mapper.readTree(it.next().asText());
										if (data.get("uri") != null) {
											rdas.add(new Metadatum());
											rdas.get(rdas.size() - 1).setMetadataStandardId(MetadataStandardIdRDAMapper.toRDA(data.get("uri").asText()));
											rdas.get(rdas.size() - 1).setDescription(data.get("label").asText());
											rdas.get(rdas.size() - 1).setAdditionalProperty("fieldId", node.get("id").asText());
											rdas.get(rdas.size() - 1).setAdditionalProperty("valueId", data.get("id").asText());
											rdaMap.put(data.get("uri").asText(), node.get("numbering").asText());
										}
									}
								} catch (IOException e) {
									logger.error(e.getMessage(), e);
								}
							} else if (rdaValue instanceof TextNode && rdaProperty.contains("identifier") && !rdaValue.asText().isEmpty()) {
								rdas.add(new Metadatum());
								rdas.get(rdas.size() - 1).setMetadataStandardId(MetadataStandardIdRDAMapper.toRDA(rdaValue.asText()));
								rdas.get(rdas.size() - 1).setAdditionalProperty("identifierId", node.get("id").asText());
								rdaMap.put(rdaValue.asText(), node.get("numbering").asText());
							}
							break;
						case DESCRIPTION:
							if (!rdaValue.asText().isEmpty()) {
								Metadatum rda = getRelative(rdas, rdaMap, node.get("numbering").asText());
								if (rda != null) {
									rda.setDescription(rdaValue.asText());
									rda.setAdditionalProperty("descriptionId", node.get("id").asText());
								} else {
									rdas.stream().filter(rda1 -> rda1.getDescription() == null || rda1.getDescription().isEmpty()).forEach(rda1 -> rda1.setDescription(rdaValue.asText()));
								}
							}
							break;
						case LANGUAGE:
							String language = rdaValue.asText();
							Metadatum.Language lang = Metadatum.Language.fromValue(language);
							Metadatum rda = getRelative(rdas, rdaMap, node.get("numbering").asText());
							if (rda != null) {
								rda.setLanguage(lang);
								rda.setAdditionalProperty("languageId", node.get("id").asText());
							} else {
								rdas.forEach(rda1 -> rda1.setLanguage(lang));
							}
							break;
					}
				}
			}

		}

		return rdas;
	}

	public static Map<String, String> toProperties(List<Metadatum> rdas) {
		Map<String, String> properties = new HashMap<>();
		List<Object> standardIds = new ArrayList<>();
		ObjectMapper mapper = new ObjectMapper();
		rdas.forEach(rda -> {
			rda.getAdditionalProperties().entrySet().forEach(entry -> {
				try {
					switch (entry.getKey()) {
						case "fieldId":
							Map<String, String> metadata = toMap(rda);
							standardIds.add(metadata);
							properties.put(entry.getValue().toString(), mapper.writeValueAsString(standardIds));
							break;
						case "identifierId":
							properties.put(entry.getValue().toString(), rda.getMetadataStandardId().getIdentifier());
							break;
						case "descriptionId":
							properties.put(entry.getValue().toString(), rda.getDescription());
							break;
						case "languageId":
							if (rda.getLanguage() != null) {
								properties.put(entry.getValue().toString(), rda.getLanguage().value());
							}
							break;
					}
				}catch (Exception e) {
					logger.error(e.getMessage(), e);
				}
			});
		});

		return properties;
	}

	public static Metadatum toRDA(JsonNode node) {
		Metadatum rda = new Metadatum();
		String rdaProperty = node.get("rdaProperty").asText();
		JsonNode rdaValue = node.get("value");
		if (rdaProperty.contains("metadata_standard_id")) {
			if (rdaValue instanceof ArrayNode) {
				for (Iterator<JsonNode> it = rdaValue.elements(); it.hasNext(); ) {
					JsonNode data = it.next();
					if (data.get("uri") != null) {
						rda.setMetadataStandardId(MetadataStandardIdRDAMapper.toRDA(data.get("uri").asText()));
					}
				}
			}
		} else if (rdaProperty.contains("description")) {
			rda.setDescription(rdaValue.asText());
		} else if (rdaProperty.contains("language")) {
			String language = rdaValue.asText();
			Metadatum.Language lang = Metadatum.Language.fromValue(language);
			rda.setLanguage(lang);
		}

		return rda;
	}

	private static Metadatum getRelative(List<Metadatum> rdas, Map<String, String> rdaMap, String numbering) {
		String target = rdaMap.entrySet().stream().filter(entry -> MyStringUtils.getFirstDifference(entry.getValue(), numbering) > 0)
							  .max(Comparator.comparingInt(entry -> MyStringUtils.getFirstDifference(entry.getValue(), numbering))).map(Map.Entry::getKey).orElse("");
		return rdas.stream().filter(rda -> rda.getMetadataStandardId().getIdentifier().equals(target)).distinct().findFirst().orElse(null);
	}

	private enum PropertyName {
		METADATA_STANDARD_ID("metadata_standard_id"),
		DESCRIPTION("description"),
		LANGUAGE("language");

		private final String name;

		PropertyName(String name) {
			this.name = name;
		}

		public String getName() {
			return name;
		}
	}

	private static Map<String, String> toMap(Metadatum rda) {
		Map<String, String> result = new HashMap<>();

		ObjectMapper mapper = new ObjectMapper();

		Map<String, Object> metadata = mapper.convertValue(rda, Map.class);

		Map<String, String> additionalProperties = mapper.convertValue(metadata.get("additional_properties"), Map.class);

		String id = additionalProperties.remove("valueId");
		additionalProperties.clear();
		additionalProperties.put("id", id);

		Map<String, String> metadataStandardId = mapper.convertValue(metadata.get("metadata_standard_id"), Map.class);

		String url = metadataStandardId.remove("identifier");
		metadataStandardId.remove("type");
		metadataStandardId.put("uri", url);

		metadata.remove("additional_properties");
		metadata.remove("metadata_standard_id");

		Map<String, String> newMetadata = metadata.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue().toString()));

		String label = newMetadata.remove("description");
		newMetadata.put("label", label);

		result.putAll(newMetadata);
		result.putAll(metadataStandardId);
		result.putAll(additionalProperties);

		return result;
	}
}
