
package eu.dnetlib.dhp.schema.oaf.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import eu.dnetlib.dhp.schema.oaf.*;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.io.InputStream;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import static org.junit.jupiter.api.Assertions.*;

/**
 * The type Merge test.
 */
class MergeTest {

	/**
	 * The Oaf.
	 */
	OafEntity oaf;

	/**
	 * Sets up.
	 */
	@BeforeEach
	public void setUp() {
		oaf = new Publication();
	}

	/**
	 * Merge lists test.
	 */
	@Test
	@SuppressWarnings("unchecked")
	void mergeListsTest() {

		// string list merge test
		List<String> a = Arrays.asList("a", "b", "c", "e");
		List<String> b = Arrays.asList("a", "b", "c", "d");
		List<String> c = null;

		System.out.println("merge result 1 = " + OafUtils.mergeLists(a, b));

		System.out.println("merge result 2 = " + OafUtils.mergeLists(a, c));

		System.out.println("merge result 3 = " + OafUtils.mergeLists(c, c));
	}

	/**
	 * Merge publication collected from test.
	 */
	@Test
	void mergePublicationCollectedFromTest() {

		Publication a = publication();
		Publication b = publication();

		a.setCollectedfrom(Arrays.asList(setKV("a", "open"), setKV("b", "closed")));
		b.setCollectedfrom(Arrays.asList(setKV("A", "open"), setKV("b", "Open")));

		a = OafUtils.mergeOAF(a, b);


		assertNotNull(a.getCollectedfrom());
		assertEquals(3, a.getCollectedfrom().size());
	}

	/**
	 * Load resource result list.
	 *
	 * @param <T>   the type parameter
	 * @param path  the path
	 * @param clazz the clazz
	 * @return the list
	 * @throws Exception the exception
	 */
	private <T extends Result> List<T> loadResourceResult(final String path, final Class<T> clazz ) throws Exception {
		final ObjectMapper mapper = new ObjectMapper();
		final InputStream str = Objects.requireNonNull(getClass().getResourceAsStream(path));
		// LOAD test publications
		return IOUtils.readLines(str).stream().map(s -> {
			try {
				return mapper.readValue(s, clazz);
			} catch (IOException e) {
				return null;
			}
		}).filter(Objects::nonNull).collect(Collectors.toList());
	}


	/**
	 * Apply to any test list result the same pid of the enrichment instance
	 *
	 * @param source                      the source
	 * @param enrichment                  the enrichment
	 * @param overrideAlternateIdentifier the override alternate identifier
	 */
	private  <T extends Result> void updatePidIntoPublicationInstance(final List<T> source, final List<T>enrichment, final boolean overrideAlternateIdentifier) {
		for(int i = 0 ; i< source.size(); i++) {
			final Result currentPub = source.get(i);
			final Result currentEnrichment = enrichment.get(i);
			final Instance currentInstance = Objects.requireNonNull(currentPub.getInstance()).get(0);
			if (overrideAlternateIdentifier)
				currentInstance.setAlternateIdentifier(Objects.requireNonNull(currentEnrichment.getInstance()).get(0).getPid());
			else
				currentInstance.setPid(Objects.requireNonNull(currentEnrichment.getInstance()).get(0).getPid());
		}
	}


	private <T extends  Result> void applyAndVerifyEnrichment(final List<T> l1, final List<T> l2) {
		// Apply Merge and verify that enrichments works
		for(int i = 0 ; i< l1.size(); i++) {
			final Result currentPub = l2.get(i);
			final Result currentEnrichment = l1.get(i);
			final Result result = OafUtils.mergeOAF(currentPub, currentEnrichment);
			assertEquals(1, result.getInstance().size());
			final Instance currentInstance = Objects.requireNonNull(result.getInstance()).get(0);
			assertNotNull(currentInstance.getMeasures());
			assertNotNull(result.getTitle());
			assertFalse(OafUtils.isAnEnrichment(result));
		}

	}

	/**
	 * Test the merge of the APC at the level of the result and the instance.
	 *
	 * @throws Exception the exception
	 */

	@Test
	void testAPCMerge() throws Exception {
		List<Publication> publications = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/publication_apc.json", Publication.class);
		System.out.println(publications.size());
		publications.forEach(p -> assertEquals(1, p.getInstance().size()));
		publications.forEach(p -> assertTrue(p.getProcessingchargeamount() != null ));
		publications.forEach(p -> assertTrue(p.getProcessingchargecurrency() != null ));
		publications.forEach(p -> assertTrue(StringUtils.isNotBlank(p.getProcessingchargeamount().getValue() )));
		publications.forEach(p -> assertTrue(StringUtils.isNotBlank(p.getProcessingchargecurrency().getValue() )));

		publications.forEach(p -> p.getInstance().stream()
				.forEach(i -> assertTrue(i.getProcessingchargeamount() != null)));
		publications.forEach(p -> p.getInstance().stream()
				.forEach(i -> assertTrue(i.getProcessingchargecurrency() != null)));

		publications.forEach(p -> p.getInstance().stream()
				.forEach(i -> assertTrue(StringUtils.isNotBlank(i.getProcessingchargeamount().getValue()))));
		publications.forEach(p -> p.getInstance().stream()
				.forEach(i -> assertTrue(StringUtils.isNotBlank(i.getProcessingchargecurrency().getValue()))));

		Result p1 = publications.get(0);
		Result p2 = publications.get(1);

		p1 = OafUtils.mergeOAF(p1, p2);

		assertEquals("1721.47", p1.getProcessingchargeamount().getValue());
		assertEquals("EUR", p1.getProcessingchargecurrency().getValue());

		assertEquals(2 , p1.getInstance().size());

		p1.getInstance().stream().forEach(i -> assertTrue(i.getProcessingchargeamount() != null));
		p1.getInstance().stream().forEach(i -> assertTrue(i.getProcessingchargecurrency() != null));

		p1.getInstance().stream().anyMatch(i -> i.getProcessingchargeamount().getValue().equals("2000.47"));
		p1.getInstance().stream().anyMatch(i -> i.getProcessingchargeamount().getValue().equals("1721.47"));

		p1.getInstance().stream().anyMatch(i -> i.getProcessingchargecurrency().getValue().equals("EUR"));
		p1.getInstance().stream().anyMatch(i -> i.getProcessingchargecurrency().getValue().equals("USD"));
		System.out.println(new ObjectMapper().writeValueAsString(p1));
	}
	@Test
	void testAPCMerge2() throws Exception {
		List<Publication> publications = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/publication_apc2.json", Publication.class);
		System.out.println(publications.size());
		publications.forEach(p -> assertEquals(1, p.getInstance().size()));
		assertTrue(publications.get(0).getProcessingchargeamount() != null );
		assertTrue(publications.get(0).getProcessingchargecurrency() != null );
		assertTrue(publications.get(1).getProcessingchargeamount() == null );

		Result p1 = publications.get(1);
		Result p2 = publications.get(0);

		//merge visible record with OpenAPC
		p1 = (Result) OafUtils.mergeOAF(p1, p2);

		assertFalse(p1.getDataInfo().getInvisible());
		assertEquals("1721.47", p1.getProcessingchargeamount().getValue());
		assertEquals("EUR", p1.getProcessingchargecurrency().getValue());

		assertEquals(2 , p1.getInstance().size());
		p1.getInstance().stream().anyMatch(i -> i.getProcessingchargeamount() != null);

		p1.getInstance().stream().anyMatch(i -> i.getProcessingchargecurrency() != null);

		assertEquals("1721.47", p1.getInstance().stream().filter(i -> i.getProcessingchargeamount() != null)
						.collect(Collectors.toList()).get(0).getProcessingchargeamount().getValue());

		assertEquals("EUR", p1.getInstance().stream().filter(i -> i.getProcessingchargeamount() != null)
				.collect(Collectors.toList()).get(0).getProcessingchargecurrency().getValue());
		assertFalse(p1.getDataInfo().getInvisible());
		System.out.println(new ObjectMapper().writeValueAsString(p1));

		//merge OpenAPC with visible record

		p2 = (Result) OafUtils.mergeOAF(p2, p1);

		assertFalse(p2.getDataInfo().getInvisible());
		assertEquals("1721.47", p2.getProcessingchargeamount().getValue());
		assertEquals("EUR", p2.getProcessingchargecurrency().getValue());

		assertEquals(2 , p2.getInstance().size());
		p2.getInstance().stream().anyMatch(i -> i.getProcessingchargeamount() != null);

		p2.getInstance().stream().anyMatch(i -> i.getProcessingchargecurrency() != null);

		assertEquals("1721.47", p2.getInstance().stream().filter(i -> i.getProcessingchargeamount() != null)
				.collect(Collectors.toList()).get(0).getProcessingchargeamount().getValue());

		assertEquals("EUR", p2.getInstance().stream().filter(i -> i.getProcessingchargeamount() != null)
				.collect(Collectors.toList()).get(0).getProcessingchargecurrency().getValue());

	}

	/**
	 * Test enrichment function.
	 *
	 * @throws Exception the exception
	 */
	@Test
	void testEnrichment() throws Exception {


		// 1 TEST UPDATING PID INSTANCE AND MERGE CURRENT PUBLICATION WITH ENRICHMENT
		// LOAD test publications
		List<Publication> publications = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/publications.json", Publication.class);
		// Assert that each publication has only one instance and inside that all the measure field is empty
		publications.forEach(p -> {
			assertEquals(1, p.getInstance().size());
			final Instance currentInstance = Objects.requireNonNull(p.getInstance()).get(0);
			assertNull(currentInstance.getMeasures());
		});

		// LOAD test enrichments
		List<Publication> enrichment = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/enrichment.json", Publication.class);

		updatePidIntoPublicationInstance(publications, enrichment, false);
		applyAndVerifyEnrichment(publications, enrichment);


		// 2 TEST UPDATING ALTERNATE ID INSTANCE AND MERGE CURRENT PUBLICATION WITH ENRICHMENT
		publications = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/publications.json", Publication.class);
		enrichment = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/enrichment.json", Publication.class);
		updatePidIntoPublicationInstance(publications, enrichment, true);
		applyAndVerifyEnrichment(publications, enrichment);


		// 3 TEST UPDATING PID INSTANCE AND MERGE ENRICHMENT WITH CURRENT PUBLICATION
		publications = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/publications.json", Publication.class);
		enrichment = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/enrichment.json", Publication.class);
		updatePidIntoPublicationInstance(publications, enrichment, false);
		applyAndVerifyEnrichment( enrichment, publications);

		// 4 TEST UPDATING ALTERNATE ID INSTANCE AND MERGE ENRICHMENT WITH CURRENT PUBLICATION
		publications = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/publications.json", Publication.class);
		enrichment = loadResourceResult("/eu/dnetlib/dhp/schema/oaf/utils/enrichment.json", Publication.class);
		updatePidIntoPublicationInstance(publications, enrichment, true);
		applyAndVerifyEnrichment( enrichment, publications);


	}


	/**
	 * Merge publication date of acceptance test both present.
	 */
	@Test
	void mergePublicationDateOfAcceptanceTest_bothPresent() {

		Publication a = publication();
		Publication b = publication();

		a.setDateofacceptance(field("2021-06-18"));
		b.setDateofacceptance(field("2021-06-19"));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getDateofacceptance());
		assertEquals("2021-06-18", a.getDateofacceptance().getValue());
	}

	/**
	 * Merge publication date of acceptance test both present 1.
	 */
	@Test
	void mergePublicationDateOfAcceptanceTest_bothPresent_1() {

		Publication a = publication("0.8");
		Publication b = publication("0.9");

		a.setDateofacceptance(field("2021-06-18"));
		b.setDateofacceptance(field("2021-06-19"));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getDateofacceptance());
		assertEquals("2021-06-19", a.getDateofacceptance().getValue());
	}

	/**
	 * Merge publication date of acceptance test both present 2.
	 */
	@Test
	void mergePublicationDateOfAcceptanceTest_bothPresent_2() {

		Publication a = publication("0.9");
		Publication b = publication("0.8");

		a.setDateofacceptance(field("2021-06-18"));
		b.setDateofacceptance(field("2021-06-19"));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getDateofacceptance());
		assertEquals("2021-06-18", a.getDateofacceptance().getValue());
	}

	/**
	 * Merge publication date of acceptance test left missing.
	 */
	@Test
	void mergePublicationDateOfAcceptanceTest_leftMissing() {

		Publication a = publication();
		Publication b = publication();

		b.setDateofacceptance(field("2021-06-19"));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getDateofacceptance());
		assertEquals("2021-06-19", a.getDateofacceptance().getValue());
	}

	/**
	 * Merge publication date of acceptance test left missing 1.
	 */
	@Test
	void mergePublicationDateOfAcceptanceTest_leftMissing_1() {

		Publication a = publication("0.9");
		Publication b = publication("0.8");

		b.setDateofacceptance(field("2021-06-19"));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getDateofacceptance());
		assertEquals("2021-06-19", a.getDateofacceptance().getValue());
	}

	/**
	 * Merge publication date of acceptance test left missing 2.
	 */
	@Test
	void mergePublicationDateOfAcceptanceTest_leftMissing_2() {

		Publication a = publication("0.8");
		Publication b = publication("0.9");

		b.setDateofacceptance(field("2021-06-19"));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getDateofacceptance());
		assertEquals("2021-06-19", a.getDateofacceptance().getValue());
	}

	/**
	 * Merge publication date of acceptance test right missing.
	 */
	@Test
	void mergePublicationDateOfAcceptanceTest_rightMissing() {

		Publication a = publication();
		Publication b = publication();

		a.setDateofacceptance(field("2021-06-19"));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getDateofacceptance());
		assertEquals("2021-06-19", a.getDateofacceptance().getValue());
	}

	/**
	 * Merge publication date of acceptance test right missing 1.
	 */
	@Test
	void mergePublicationDateOfAcceptanceTest_rightMissing_1() {

		Publication a = publication("0.8");
		Publication b = publication("0.9");

		a.setDateofacceptance(field("2021-06-19"));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getDateofacceptance());
		assertEquals("2021-06-19", a.getDateofacceptance().getValue());
	}

	/**
	 * Merge publication date of acceptance test right missing 2.
	 */
	@Test
	void mergePublicationDateOfAcceptanceTest_rightMissing_2() {

		Publication a = publication("0.9");
		Publication b = publication("0.8");

		a.setDateofacceptance(field("2021-06-19"));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getDateofacceptance());
		assertEquals("2021-06-19", a.getDateofacceptance().getValue());
	}

	/**
	 * Merge publication subject test.
	 */
	@Test
	void mergePublicationSubjectTest() {

		Publication a = publication();
		Publication b = publication();

		a.setSubject(Arrays.asList(setSP("a", "open", "classe"), setSP("b", "open", "classe")));
		b.setSubject(Arrays.asList(setSP("A", "open", "classe"), setSP("c", "open", "classe")));

		a = OafUtils.mergeOAF(a,b);

		assertNotNull(a.getSubject());
		assertEquals(3, a.getSubject().size());
	}

	/**
	 * Merge relation test.
	 */
	@Test
	void mergeRelationTest() {

		Relation a = createRel(null, null);
		Relation b = createRel(null, null);
		a = OafUtils.mergeOAF(a,b);
		assertEquals(a, b);

		a = createRel(true, null);
		b = createRel(null, null);
		a = OafUtils.mergeOAF(a,b);
		assertEquals(true, a.getValidated());

		a = createRel(true, null);
		b = createRel(false, null);
		a = OafUtils.mergeOAF(a,b);
		assertEquals(true, a.getValidated());

		a = createRel(true, null);
		b = createRel(true, "2016-04-05T12:41:19.202Z");
		a = OafUtils.mergeOAF(a,b);
		assertEquals("2016-04-05T12:41:19.202Z", a.getValidationDate());

		a = createRel(true, "2016-05-07T12:41:19.202Z");
		b = createRel(true, "2016-04-05T12:41:19.202Z");
		a = OafUtils.mergeOAF(a,b);
		assertEquals("2016-04-05T12:41:19.202Z", a.getValidationDate());

		a = createRel(true, "2020-09-10 11:08:52");
		b = createRel(true, "2021-09-10 11:08:52");
		a = OafUtils.mergeOAF(a,b);
		assertEquals("2020-09-10 11:08:52", a.getValidationDate());

		a = createRel(true, "2021-03-16T10:32:42Z");
		b = createRel(true, "2020-03-16T10:32:42Z");
		a = OafUtils.mergeOAF(a,b);
		assertEquals("2020-03-16T10:32:42Z", a.getValidationDate());


	}

	/**
	 * Merge relation test parse exception.
	 */
	@Test
	void mergeRelationTestParseException() {
		assertThrows(DateTimeParseException.class, () -> {
			Relation a = createRel(true, "Once upon a time ...");
			Relation b = createRel(true, "... in a far away land");
			a = OafUtils.mergeOAF(a,b);
		});
	}

	/**
	 * Create rel relation.
	 *
	 * @param validated      the validated
	 * @param validationDate the validation date
	 * @return the relation
	 */
	private Relation createRel(Boolean validated, String validationDate) {
		Relation rel = new Relation();
		rel.setSource("1");
		rel.setTarget("2");
		rel.setRelType("reltype");
		rel.setSubRelType("subreltype");
		rel.setRelClass("relclass");
		rel.setValidated(validated);
		rel.setValidationDate(validationDate);
		return rel;
	}

	/**
	 * Sets kv.
	 *
	 * @param key   the key
	 * @param value the value
	 * @return the kv
	 */
	private KeyValue setKV(final String key, final String value) {

		KeyValue k = new KeyValue();

		k.setKey(key);
		k.setValue(value);

		return k;
	}

	/**
	 * Sets sp.
	 *
	 * @param value     the value
	 * @param schema    the schema
	 * @param classname the classname
	 * @return the sp
	 */
	private StructuredProperty setSP(
		final String value, final String schema, final String classname) {
		StructuredProperty s = new StructuredProperty();
		s.setValue(value);
		Qualifier q = new Qualifier();
		q.setClassname(classname);
		q.setClassid(classname);
		q.setSchemename(schema);
		q.setSchemeid(schema);
		s.setQualifier(q);
		return s;
	}

	/**
	 * Field field.
	 *
	 * @param <T>   the type parameter
	 * @param value the value
	 * @return the field
	 */
	private <T> Field<T> field(T value) {
		Field<T> f = new Field();
		f.setValue(value);
		return f;
	}

	/**
	 * Publication publication.
	 *
	 * @return the publication
	 */
	private Publication publication() {
		Publication p = new Publication();
		p.setDataInfo(df("0.9"));
		return p;
	}

	/**
	 * Publication publication.
	 *
	 * @param trust the trust
	 * @return the publication
	 */
	private Publication publication(String trust) {
		Publication p = new Publication();
		p.setDataInfo(df(trust));
		return p;
	}

	/**
	 * Df data info.
	 *
	 * @param trust the trust
	 * @return the data info
	 */
	private DataInfo df(String trust) {
		DataInfo d = new DataInfo();
		d.setTrust(trust);
		return d;
	}
}
