package ca.uhn.fhir.parser;

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

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.jupiter.api.AfterAll;
import org.junit.Assert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.Composition;
import ca.uhn.fhir.model.dstu.resource.Composition.Section;
import ca.uhn.fhir.model.dstu.resource.Condition;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.Practitioner;
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
import ca.uhn.fhir.model.dstu.valueset.ConditionStatusEnum;
import ca.uhn.fhir.model.dstu.valueset.NameUseEnum;
import ca.uhn.fhir.model.dstu.valueset.PractitionerRoleEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory;
import ca.uhn.fhir.util.TestUtil;

/**
 * Initially contributed by Alexander Kley for bug #29
 */
public class ContainedResourceEncodingTest {

	private static Logger ourLog = LoggerFactory.getLogger(ContainedResourceEncodingTest.class);

	private FhirContext ctx;

	private Composition comp;

	private Practitioner author;

	private Patient patient;

	private final String patFamName1 = "FirstFamilyName";

	private final String patGivName1 = "FirstGivenName";

	@BeforeEach
	public void initTest() {
		ourLog.info("[initTest]");

		initPatient();
		initAuthor();
		initComposition();
		this.ctx = FhirContext.forDstu1();

	}

	private void initComposition() {
		// new ConditionList

		final Condition c = new Condition();
		c.setId(UUID.randomUUID().toString());
		c.setNotes("This is a note");
		c.setSubject(new ResourceReferenceDt(this.patient));
		c.setCode(new CodeableConceptDt("mySystem", "theCode"));
		c.setStatus(ConditionStatusEnum.CONFIRMED);

		// new General Note Section
		final Section generalNoteSection = new Section();
		generalNoteSection.setElementSpecificId("Note");
		generalNoteSection.setTitle("Note");
		generalNoteSection.setContent(new ResourceReferenceDt(c));

		// new SectionList
		final List<Section> sectionList = new ArrayList<Section>();
		sectionList.add(generalNoteSection);

		// fill composition
		this.comp = new Composition();
		this.comp.addAuthor().setResource(this.author);
		this.comp.setSubject(new ResourceReferenceDt(this.patient));
		this.comp.setSection(sectionList);
	}

	private void initPatient() {
		this.patient = new Patient();
		this.patient.setId(new IdDt(UUID.randomUUID().toString()));
		this.patient.addIdentifier().setSystem("http://example.com/fictitious-mrns").setValue("MRN001");
		this.patient.setGender(AdministrativeGenderCodesEnum.M);
		this.patient.addName().setUse(NameUseEnum.OFFICIAL).addFamily(this.patFamName1).addGiven(this.patGivName1);

	}

	private void initAuthor() {
		this.author = new Practitioner();
		this.author.setId(new IdDt(UUID.randomUUID().toString()));
		this.author.addIdentifier().setSystem("DoctorID").setValue("4711");
		this.author.addRole(PractitionerRoleEnum.DOCTOR);
		this.author.setName(new HumanNameDt().addFamily("Mueller").addGiven("Klaus").addPrefix("Prof. Dr."));

	}

	@Test
	public void testPatient() {
		ourLog.debug("[xmlEncoding] encode resource to xml.");

		/**
		 * This works fine, although patient instance is modifing from encoder
		 */
		final String expectedPatientXml = this.ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(this.patient);
		ourLog.debug("[xmlEncoding] first encoding: {}", expectedPatientXml);
		final String actualPatientXml = this.ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(this.patient);
		// second encoding - xml is corrupt - i.e.: patient content 4 times! should be the same as after first encoding!
		ourLog.debug("[xmlEncoding] second encoding: {}", actualPatientXml);

		assertEquals(expectedPatientXml.length(), actualPatientXml.length());
		Assert.assertArrayEquals(expectedPatientXml.getBytes(), actualPatientXml.getBytes());

	}

	@Test
	public void testComposition() {

		IParser parser = this.ctx.newXmlParser().setPrettyPrint(true);

		assertEquals(0, this.comp.getContained().getContainedResources().size());

		/**
		 * This doesn't works, secund encoding creates corrupt xml
		 */
		final String expectedCompXml = parser.encodeResourceToString(this.comp);
		ourLog.debug("[xmlEncoding] first encoding: {}", expectedCompXml);

		assertEquals(0, this.comp.getContained().getContainedResources().size());

		final String actualCompXml = parser.encodeResourceToString(this.comp);

		assertEquals(0, this.comp.getContained().getContainedResources().size());

		// second encoding - xml could not be parsed back to compositon - i.e.: patient content 4 times! should be the same
		// as after first encoding!
		ourLog.debug("[xmlEncoding] second encoding: {}", actualCompXml);

		final String thirdCompXml = parser.encodeResourceToString(this.comp);

		assertEquals(0, this.comp.getContained().getContainedResources().size());

		// third encoding - xml could not be parsed back to compositon i.e.: patient content 4 times! should be the same as
		// afterfirst encoding!
		ourLog.debug("[xmlEncoding] third encoding: {}", thirdCompXml);

		assertEquals(expectedCompXml.length(), actualCompXml.length());
		Assert.assertArrayEquals(expectedCompXml.getBytes(), actualCompXml.getBytes());

	}

	@Test
	public void testBundleWithContained() {

		DiagnosticReport dr = new DiagnosticReport();
		dr.setId(new IdDt("DiagnosticReport", "123"));

		Observation observation = new Observation();

		CodeableConceptDt obsName = new CodeableConceptDt();
		obsName.setText("name");
		observation.setName(obsName);

		dr.addResult().setResource(observation);

		ArrayList<ResourceReferenceDt> performers = new ArrayList<ResourceReferenceDt>();
		ResourceReferenceDt performer = new ResourceReferenceDt();

		Practitioner p = new Practitioner();
		p.setId("#" + IdDt.newRandomUuid());
		p.addIdentifier().setSystem("DoctorID").setValue("4711");
		p.addRole(PractitionerRoleEnum.DOCTOR);
		p.setName(new HumanNameDt().addFamily("Mueller").addGiven("Klaus").addPrefix("Prof. Dr."));

		performer.setResource(p);
		performers.add(performer);
		observation.setPerformer(performers);

		IParser parser = this.ctx.newXmlParser().setPrettyPrint(true);
		String xml = parser.encodeResourceToString(dr);
		ourLog.info(xml);
		Assert.assertTrue(xml.contains("Mueller"));
		
		List<IBaseResource> list = new ArrayList<IBaseResource>();
		list.add(dr);

		IVersionSpecificBundleFactory factory = ctx.newBundleFactory();
		factory.initializeBundleFromResourceList("", list, "http://foo", "http://foo", 2, null);
		Bundle bundle = factory.getDstu1Bundle();

		parser = this.ctx.newXmlParser().setPrettyPrint(true);
		xml = parser.encodeBundleToString(bundle);
		ourLog.info(xml);
		Assert.assertTrue(xml.contains("Mueller"));

	}

	@Test
	public void testBundleWithContainedWithNoIdDt() {

		DiagnosticReport dr = new DiagnosticReport();
		dr.setId(new IdDt("DiagnosticReport", "123"));

		Observation observation = new Observation();

		CodeableConceptDt obsName = new CodeableConceptDt();
		obsName.setText("name");
		observation.setName(obsName);

		ResourceReferenceDt result = dr.addResult();
		result.setResource(observation);

		ArrayList<ResourceReferenceDt> performers = new ArrayList<ResourceReferenceDt>();
		ResourceReferenceDt performer = new ResourceReferenceDt();

		Practitioner p = new Practitioner();
		// no idDt on practitioner p
		p.addIdentifier().setSystem("DoctorID").setValue("4711");
		p.addRole(PractitionerRoleEnum.DOCTOR);
		p.setName(new HumanNameDt().addFamily("Mueller").addGiven("Klaus").addPrefix("Prof. Dr."));

		performer.setResource(p);
		performers.add(performer);
		observation.setPerformer(performers);

		List<IBaseResource> list = new ArrayList<IBaseResource>();
		list.add(dr);

		IVersionSpecificBundleFactory factory = ctx.newBundleFactory();
		factory.initializeBundleFromResourceList("", list, "http://foo", "http://foo", 2, null);
		Bundle bundle = factory.getDstu1Bundle();

		IParser parser = this.ctx.newXmlParser().setPrettyPrint(true);
		String xml = parser.encodeBundleToString(bundle);
		ourLog.info(xml);
		Assert.assertTrue(xml.contains("Mueller"));

	}


	@AfterAll
	public static void afterClassClearContext() {
		TestUtil.clearAllStaticFieldsForUnitTest();
	}

}
