package eu.dnetlib.grid.process.utils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.commons.lang3.StringUtils;

import com.fasterxml.jackson.databind.ObjectMapper;

import eu.dnetlib.data.proto.FieldTypeProtos.DataInfo;
import eu.dnetlib.data.proto.FieldTypeProtos.KeyValue;
import eu.dnetlib.data.proto.FieldTypeProtos.Qualifier;
import eu.dnetlib.data.proto.FieldTypeProtos.StringField;
import eu.dnetlib.data.proto.FieldTypeProtos.StructuredProperty;
import eu.dnetlib.data.proto.KindProtos.Kind;
import eu.dnetlib.data.proto.OafProtos.Oaf;
import eu.dnetlib.data.proto.OafProtos.OafEntity;
import eu.dnetlib.data.proto.OafProtos.OafRel;
import eu.dnetlib.data.proto.OrganizationProtos.Organization;
import eu.dnetlib.data.proto.RelTypeProtos.RelType;
import eu.dnetlib.data.proto.RelTypeProtos.SubRelType;
import eu.dnetlib.data.proto.TypeProtos.Type;
import eu.dnetlib.grid.process.model.GridLabel;
import eu.dnetlib.grid.process.model.GridOrganization;
import eu.dnetlib.grid.process.model.GridRel;
import eu.dnetlib.grid.process.model.GridResponse;
import eu.dnetlib.miscutils.datetime.DateUtils;
import eu.dnetlib.miscutils.functional.hash.Hashing;

public class GridUtils {

	private static final String PRIMARY_TRUST_LEVEL = "0.91";

	private static final String SECONDARY_TRUST_LEVEL = "0.89";

	public static Stream<GridOrganization> streamOrganizations(final String jsonFile) {
		try {
			return StreamSupport.stream(GridUtils.getOrganizations(new FileInputStream(jsonFile)).spliterator(), false);
		} catch (final FileNotFoundException e) {
			e.printStackTrace();
			return Stream.empty();
		}
	}

	protected static Iterable<GridOrganization> getOrganizations(final InputStream input) {
		try {
			return new ObjectMapper().readValue(input, GridResponse.class).getInstitutes();
		} catch (final Throwable e) {
			e.printStackTrace();
			return new ArrayList<>();
		}
	}

	public static List<Oaf> toProtos(final GridOrganization org, final Map<String, String> parents, final Datasource ds) {

		final String gridId = org.getId();
		final String parent = findParentName(org, parents);
		final String mainName = applyPrefix(parent, org.getName());
		final String shortName = findShortName(org);
		final String mainOpenaireId = calculateOpenaireId(ds.getPrefix(), gridId);
		final Set<String> alternativeNames = calculateAlternativeNames(org);

		final Map<String, String> orgNames = alternativeNames.stream()
				.map(s -> applyPrefix(parent, s))
				.collect(Collectors.toMap(
						s -> calculateOpenaireId(ds.getPrefix(), gridId, s),
						s -> s));
		orgNames.put(mainOpenaireId, mainName);

		final Qualifier country = org.getAddresses().stream()
				.map(addr -> Qualifier.newBuilder()
						.setClassid(addr.getCountry_code())
						.setClassname(addr.getCountry())
						.setSchemeid("dnet:countries")
						.setSchemename("dnet:countries"))
				.filter(q -> StringUtils.isNotBlank(q.getClassid()))
				.filter(q -> StringUtils.isNotBlank(q.getClassname()))
				.map(q -> q.build())
				.findFirst()
				.orElse(null);

		final KeyValue collectedFrom = KeyValue.newBuilder()
				.setKey(ds.getId())
				.setValue(ds.getName()).build();

		return orgNames.entrySet()
				.stream()
				.map(e -> toProtos(e.getKey(),
						gridId,
						e.getValue(),
						shortName,
						org.getLinks().stream().findFirst().orElse(""),
						country,
						e.getKey().equals(mainOpenaireId) ? alternativeNames : new HashSet<>(),
						orgNames.keySet(),
						collectedFrom,
						e.getValue().equals(mainName) ? PRIMARY_TRUST_LEVEL : SECONDARY_TRUST_LEVEL))
				.flatMap(l -> l.stream())
				.collect(Collectors.toList());
	}

	private static List<Oaf> toProtos(final String openaireId,
			final String gridId,
			final String name,
			final String shortName,
			final String url,
			final Qualifier country,
			final Set<String> alternativeNames,
			final Set<String> orgRels,
			final KeyValue collectedFrom,
			final String trust) {

		final OafEntity.Builder entity = OafEntity.newBuilder()
				.setId(openaireId)
				.addPid(StructuredProperty.newBuilder()
						.setValue(gridId)
						.setQualifier(Qualifier.newBuilder()
								.setClassid("grid")
								.setClassname("grid")
								.setSchemeid("dnet:pid_types")
								.setSchemename("dnet:pid_types")))
				.addCollectedfrom(collectedFrom)
				.setType(Type.organization)
				.setOrganization(Organization.newBuilder()
						.setMetadata(Organization.Metadata.newBuilder()
								.setLegalname(StringField.newBuilder().setValue(name))
								.setLegalshortname(StringField.newBuilder().setValue(shortName))
								.setWebsiteurl(StringField.newBuilder().setValue(url))
								.addAllAlternativeNames(
										alternativeNames.stream().map(a -> StringField.newBuilder().setValue(a).build()).collect(Collectors.toList()))
								.setCountry(country)));

		// Relations
		final List<Oaf> oafs = orgRels.stream()
				.filter(id -> !id.equals(openaireId))
				.map(id -> Oaf.newBuilder()
						.setKind(Kind.relation)
						.setLastupdatetimestamp(DateUtils.now())
						.setRel(OafRel.newBuilder()
								.setSource(openaireId)
								.setTarget(id)
								.setRelType(RelType.organizationOrganization)
								.setSubRelType(SubRelType.dedupSimilarity)
								.setRelClass("isSimilarTo")
								.setChild(false))
						.build())
				.collect(Collectors.toList());

		// Entity
		oafs.add(Oaf.newBuilder()
				.setKind(Kind.entity)
				.setLastupdatetimestamp(DateUtils.now())
				.setEntity(entity)
				.setDataInfo(DataInfo.newBuilder()
						.setTrust(trust)
						.setInferred(false)
						.setProvenanceaction(Qualifier.newBuilder()
								.setClassid("UNKNOWN")
								.setClassname("UNKNOWN")
								.setSchemeid("dnet:provenanceActions")
								.setSchemename("dnet:provenanceActions")))
				.build());

		return oafs;
	}

	private static String calculateOpenaireId(final String prefix, final String gridId) {
		return String.format("20|%s::%s", prefix, Hashing.md5(gridId));
	}

	private static String calculateOpenaireId(final String prefix, final String gridId, final String name) {
		return String.format("20|%s::%s", prefix, Hashing.md5(gridId + " " + name));
	}

	private static String applyPrefix(final String parent, final String simpleName) {
		return StringUtils.isBlank(parent) ? simpleName : parent + " - " + simpleName;
	}

	private static Set<String> calculateAlternativeNames(final GridOrganization org) {
		final Set<String> res = org.getLabels().stream()
				.map(GridLabel::getLabel)
				.collect(Collectors.toSet());
		res.addAll(org.getAcronyms());
		return res;
	}

	public static String findShortName(final GridOrganization org) {
		return org.getAcronyms()
				.stream()
				.filter(StringUtils::isNotBlank)
				.findFirst()
				.orElse(org.getName());
	}

	private static String findParentName(final GridOrganization org, final Map<String, String> parents) {
		return org.getRelationships()
				.stream()
				.filter(r -> r.getType().equalsIgnoreCase("Parent"))
				.map(GridRel::getId)
				.map(parents::get)
				.filter(StringUtils::isNotBlank)
				.findFirst()
				.orElse(null);
	}
}
