package eu.eudat.models.rda.mapper;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import eu.eudat.logic.utilities.helpers.MyStringUtils;
import eu.eudat.models.rda.Host;
import eu.eudat.models.rda.PidSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.util.*;
import java.util.stream.Collectors;

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

	public static Host toRDA(List<JsonNode> nodes, String numbering) {
		Host rda = new Host();
		for (JsonNode node: nodes) {
			String rdaProperty = node.get("rdaProperty").asText();
			if (rdaProperty.contains("host")) {
				int firstDiff = MyStringUtils.getFirstDifference(numbering, node.get("numbering").asText());
				if (firstDiff == -1 || firstDiff >= 2) {
					String rdaValue = node.get("value").asText();
					if(rdaValue == null || (rdaValue.isEmpty() && !node.get("value").isArray())){
						continue;
					}
					for (ExportPropertyName propertyName: ExportPropertyName.values()) {
						if (rdaProperty.contains(propertyName.getName())) {
							switch (propertyName) {
								case AVAILABILITY:
									rda.setAvailability(rdaValue);
									rda.setAdditionalProperty(ImportPropertyName.AVAILABILITY.getName(), node.get("id").asText());
									break;
								case BACKUP_FREQUENCY:
									rda.setBackupFrequency(rdaValue);
									rda.setAdditionalProperty(ImportPropertyName.BACKUP_FREQUENCY.getName(), node.get("id").asText());
									break;
								case BACKUP_TYPE:
									rda.setBackupType(rdaValue);
									rda.setAdditionalProperty(ImportPropertyName.BACKUP_TYPE.getName(), node.get("id").asText());
									break;
								case CERTIFIED_WITH:
									rda.setCertifiedWith(Host.CertifiedWith.fromValue(rdaValue));
									rda.setAdditionalProperty(ImportPropertyName.CERTIFIED_WITH.getName(), node.get("id").asText());
									break;
								case DESCRIPTION:
									rda.setDescription(rdaValue);
									rda.setAdditionalProperty(ImportPropertyName.DESCRIPTION.getName(), node.get("id").asText());
									break;
								case GEO_LOCATION:
									if (rdaValue.startsWith("{")) {
										try {
											rdaValue = new ObjectMapper().readValue(rdaValue, Map.class).get("id").toString();
										} catch (JsonProcessingException e) {
											logger.warn(e.getLocalizedMessage() + ". Try to pass value as is");
										}
									}
									rda.setGeoLocation(Host.GeoLocation.fromValue(rdaValue));
									rda.setAdditionalProperty(ImportPropertyName.GEO_LOCATION.getName(), node.get("id").asText());
									break;
								case PID_SYSTEM:
									try{
										Iterator<JsonNode> iter = node.get("value").elements();
										List<String> pList = new ArrayList<>();
										while(iter.hasNext()) {
											pList.add(iter.next().asText());
										}
										List<PidSystem> pidList;
										if(pList.size() == 0){
											pidList = Arrays.stream(rdaValue.replaceAll("[\\[\"\\]]","").split(","))
													.map(PidSystem::fromValue).collect(Collectors.toList());
										}
										else{
											pidList = pList.stream().map(PidSystem::fromValue).collect(Collectors.toList());
										}
										rda.setPidSystem(pidList);
										rda.setAdditionalProperty(ImportPropertyName.PID_SYSTEM.getName(), node.get("id").asText());
									}
									catch (IllegalArgumentException e){
										rda.setPidSystem(new ArrayList<>());
										break;
									}
									break;
								case STORAGE_TYPE:
									rda.setStorageType(rdaValue);
									rda.setAdditionalProperty(ImportPropertyName.STORAGE_TYPE.getName(), node.get("id").asText());
									break;
								case SUPPORT_VERSIONING:
									rda.setSupportVersioning(Host.SupportVersioning.fromValue(rdaValue));
									rda.setAdditionalProperty(ImportPropertyName.SUPPORT_VERSIONING.getName(), node.get("id").asText());
									break;
								case TITLE:
									rda.setTitle(rdaValue);
									rda.setAdditionalProperty(ImportPropertyName.TITLE.getName(), node.get("id").asText());
									break;
								case URL:
									try {
										rda.setUrl(URI.create(rdaValue));
										rda.setAdditionalProperty(ImportPropertyName.URL.getName(), node.get("id").asText());
									} catch (IllegalArgumentException e) {
										logger.warn(e.getLocalizedMessage() + ". Skipping url parsing");
									}
									break;
							}
						}
					}
				}
			}
		}

		if(rda.getTitle() == null || rda.getUrl() == null){
			return null;
		}

		return rda;
	}

	public static Map<String, String> toProperties(Host rda) {
		Map<String, String> properties = new HashMap<>();
			rda.getAdditionalProperties().entrySet().forEach(entry -> {
				try {
					ImportPropertyName importPropertyName = ImportPropertyName.fromString(entry.getKey());
					switch (importPropertyName) {
						case AVAILABILITY:
							properties.put(entry.getValue().toString(), rda.getAvailability());
							break;
						case TITLE:
							properties.put(entry.getValue().toString(), rda.getTitle());
							break;
						case DESCRIPTION:
							properties.put(entry.getValue().toString(), rda.getDescription());
							break;
						case BACKUP_FREQUENCY:
							properties.put(entry.getValue().toString(), rda.getBackupFrequency());
							break;
						case BACKUP_TYPE:
							properties.put(entry.getValue().toString(), rda.getBackupType());
							break;
						case CERTIFIED_WITH:
							properties.put(entry.getValue().toString(), rda.getCertifiedWith().value());
							break;
						case GEO_LOCATION:
							properties.put(entry.getValue().toString(), rda.getGeoLocation().value());
							break;
						case PID_SYSTEM:
							List<Object> pids = new ArrayList<>();
							ObjectMapper mapper = new ObjectMapper();
							for(PidSystem pid: rda.getPidSystem()){
								pids.add(pid.value());
							}
							if(!pids.isEmpty()){
								properties.put(entry.getValue().toString(), mapper.writeValueAsString(pids));
							}
							break;
						case STORAGE_TYPE:
							properties.put(entry.getValue().toString(), rda.getStorageType());
							break;
						case SUPPORT_VERSIONING:
							properties.put(entry.getValue().toString(), rda.getSupportVersioning().value());
							break;
						case URL:
							properties.put(entry.getValue().toString(), rda.getUrl().toString());
							break;
					}
				} catch (Exception e) {
					logger.error(e.getMessage(), e);
				}

			});

		return properties;
	}

	private enum ExportPropertyName {
		AVAILABILITY("availability"),
		BACKUP_FREQUENCY("backup_frequency"),
		BACKUP_TYPE("backup_type"),
		CERTIFIED_WITH("certified_with"),
		DESCRIPTION("description"),
		GEO_LOCATION("geo_location"),
		PID_SYSTEM("pid_system"),
		STORAGE_TYPE("storage_type"),
		SUPPORT_VERSIONING("support_versioning"),
		TITLE("title"),
		URL("url");

		private final String name;

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

		public String getName() {
			return name;
		}
	}

	private enum ImportPropertyName {
		AVAILABILITY("availabilityId"),
		BACKUP_FREQUENCY("backup_frequencyId"),
		BACKUP_TYPE("backup_typeId"),
		CERTIFIED_WITH("certified_withId"),
		DESCRIPTION("descriptionId"),
		GEO_LOCATION("geo_locationId"),
		PID_SYSTEM("pid_systemId"),
		STORAGE_TYPE("storage_typeId"),
		SUPPORT_VERSIONING("support_versioningId"),
		TITLE("titleId"),
		URL("urlId");

		private final String name;

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

		public String getName() {
			return name;
		}

		public static ImportPropertyName fromString(String name) throws Exception {
			for (ImportPropertyName importPropertyName: ImportPropertyName.values()) {
				if (importPropertyName.getName().equals(name)) {
					return importPropertyName;
				}
			}
			throw new Exception("No name available");
		}
	}
}
