package gov.va.med.pharmacy.jaxrs.inboundncpdpmessage.service.impl;

import java.io.InputStream;
import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBIntrospector;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.springframework.beans.factory.annotation.Autowired;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import gov.va.med.pharmacy.jaxrs.inboundncpdpmessage.service.InboundNCPDPMessageService;
import gov.va.med.pharmacy.ncpdpmsgs.MessageType;
import gov.va.med.pharmacy.persistence.model.InboundNcpdpMsgEntity;
import gov.va.med.pharmacy.persistence.service.InboundNcpdpMsgService;
import gov.va.med.pharmacy.utility.StreamUtilities;

/**
 * A class that accepts incoming NCPDP message from change healthcare and
 * returns a response.
 *
 */

@Consumes({ "application/xml", "text/xml" })
@Produces("application/xml")
public class InboundNCPDPMessageServiceImpl implements InboundNCPDPMessageService {

	private static final String LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd";

	private static final String EXTERNAL_PARAMETER_ENTITIES = "http://xml.org/sax/features/external-parameter-entities";

	private static final String EXTERNAL_GENERAL_ENTITIES = "http://xml.org/sax/features/external-general-entities";

	private static final String DESCRIPTION_MESSAGE_RECEVIED_DESCRIPTION = "<Description>Message Recevied.</Description>";

	private static final String EMD_NCPDP_SCRIPT_10_6_XSD = "EMD_NCPDP_SCRIPT_10_6.xsd";

	private static final String ERROR_DESCRIPTION_APPLICATION_ERROR = "<Description>Application Error</Description>";

	private static final String MESSAGE_ERROR_END = "</Error>";

	private static final String ERROR_DESCRIPTION_XSD_VALIDATION = "<Description>XSD Validation Error</Description>";

	private static final String ERROR_CODE_602 = "<Code>602</Code>";

	private static final String MESSAGE_ERROR_START = "<Error>";

	private static final String MESSAGE_BODY_END = "</Body>";

	private static final String MESSAGE_STATUS_END = "</Status>";

	private static final String CODE_000 = "<Code>000</Code>";

	private static final String MESSAGE_STATUS_START = "<Status>";

	private static final String MESSAGE_BODY_START = "<Body>";

	private static final String SENT_TIME_EBD = "</SentTime>";

	private static final String SENT_TIME_START = "<SentTime>";

	private static final String RELATES_TO_MESSAGE_ID_END_TAG = "</RelatesToMessageID>";

	private static final String RELATES_TO_MESSAGE_ID = "<RelatesToMessageID>";

	private static final String MESSAGE_ID_END = "</MessageID>";

	private static final String MESSAGE_ID_START = "<MessageID>";

	private static final String FROM_ENG_TAG = "</From>";

	private static final String FROM_QUALIFIER_P = "<From Qualifier=\"P\">";
	
	private static final String FROM_QUALIFIER_D = "<From Qualifier=\"D\">";

	private static final String TO_TAG_END = "</To>";

	private static final String TO_QUALIFIER_D = "<To Qualifier=\"D\">";
	
	private static final String TO_QUALIFIER_P = "<To Qualifier=\"P\">";

	private static final String MESSAGE_HEADER_END = "</Header>";

	private static final String MESSAGE_HEADER_START = "<Header>";

	private static final String MESSAGE_END = "</Message>";

	private static final String MESSAGE_START = "<Message version=\"010\" release=\"006\" HighestVersionSupported=\"\" xmlns=\"http://www.ncpdp.org/schema/SCRIPT\">";

	private static final String SENT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";

	private static final String INB_MSG_DATE_FORMAT = "MMddyyyy.HH.mm.ss.sss";

	private static final String READY_FOR_AUTOCHECK = "0000";

	private static final org.apache.logging.log4j.Logger LOG = org.apache.logging.log4j.LogManager.getLogger(InboundNCPDPMessageServiceImpl.class);


	@Autowired
	private InboundNcpdpMsgService inboundNcpdpMsgService;

	
	@POST
	@Path("/inBoundNCPDPMessage/")
	public String getInboundMessage(String incomingMessage) {

		Date receivedDate = new Date();

		String response = null;

		InboundNcpdpMsgEntity inboundeRx;

		String relatesToMessageID = "";

		String incomingMessageID = "";

		String messageFrom = "";

		String messageTo = "";

		String newMessageID = null;

		String sentDateString = null;
		
		StreamSource xsdStreamSource = null;
		
		Source xsdSource = null;
		
		InputStream inputStream = null;

		StringBuffer responseBuffer = new StringBuffer(MESSAGE_START);
		
		responseBuffer.append(MESSAGE_HEADER_START);

		try {

			// put the sent date in response in zulu time format.
			SimpleDateFormat sentDateFormatter = new SimpleDateFormat(SENT_DATE_FORMAT, Locale.US);


			sentDateString = sentDateFormatter.format(receivedDate);

			// for saving. The server are in central time zone so it should be consistent.
			Date date = new Date();

			// for response.
			SimpleDateFormat inbMsgDateFormatter = new SimpleDateFormat(INB_MSG_DATE_FORMAT, Locale.US);


			String dateString = inbMsgDateFormatter.format(date);
			
			

			// 9 digit random number.
			int randomNumber = ThreadLocalRandom.current().nextInt(1, 100000000 + 1);

			newMessageID = new StringBuffer(dateString).append(randomNumber).toString();

			if (incomingMessage.indexOf(MESSAGE_ID_START) != -1) {
				
				incomingMessageID = incomingMessage.substring(incomingMessage.indexOf(MESSAGE_ID_START) + 11,	incomingMessage.indexOf(MESSAGE_ID_END));

				relatesToMessageID = incomingMessageID;

			}

			if (incomingMessage.indexOf(FROM_QUALIFIER_P) != -1 || incomingMessage.indexOf(FROM_QUALIFIER_D) != -1) {

				if (incomingMessage.indexOf(FROM_QUALIFIER_P) != -1) {

					messageFrom = incomingMessage.substring(incomingMessage.indexOf(FROM_QUALIFIER_P) + 20,	incomingMessage.indexOf(FROM_ENG_TAG));
					
				} else if (incomingMessage.indexOf(FROM_QUALIFIER_D) != -1) {

					messageFrom = incomingMessage.substring(incomingMessage.indexOf(FROM_QUALIFIER_D) + 20,	incomingMessage.indexOf(FROM_ENG_TAG));
				}

			}

			if (incomingMessage.indexOf(TO_QUALIFIER_D) != -1 || incomingMessage.indexOf(TO_QUALIFIER_P) != -1) {

				if (incomingMessage.indexOf(TO_QUALIFIER_D) != -1) {

					messageTo = incomingMessage.substring(incomingMessage.indexOf(TO_QUALIFIER_D) + 18,	incomingMessage.indexOf(TO_TAG_END));
					
				} else if (incomingMessage.indexOf(TO_QUALIFIER_P) != -1) {

					messageTo = incomingMessage.substring(incomingMessage.indexOf(TO_QUALIFIER_P) + 18,	incomingMessage.indexOf(TO_TAG_END));
				}

			}

			// First do xml validation against xsd using sax parser.

			SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

			// fortify get inputstream and then close it 
			inputStream = this.getClass().getClassLoader().getResourceAsStream(EMD_NCPDP_SCRIPT_10_6_XSD);
			
			xsdStreamSource = new StreamSource(inputStream);
			
			xsdSource = xsdStreamSource;

			Schema schema = schemaFactory.newSchema(xsdSource);

			SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();

			saxParserFactory.setSchema(schema);

			SAXParser saxparser =  saxParserFactory.newSAXParser();
			
			XMLReader xmlReader = saxparser.getXMLReader();
			
            // fortify XXE Fix.
			xmlReader.setFeature(EXTERNAL_GENERAL_ENTITIES, false); 
			xmlReader.setFeature(EXTERNAL_PARAMETER_ENTITIES, false); 
			xmlReader.setFeature(LOAD_EXTERNAL_DTD,false); 
 
			
			InputSource inputparseXML = new InputSource(new StringReader(incomingMessage));
			inputparseXML.setEncoding("UTF-8");

			xmlReader.parse(inputparseXML);

			// if validation is successful proceed further.
		

			inboundeRx = new InboundNcpdpMsgEntity();


			inboundeRx.setMessageId(incomingMessageID);

			// relates to message ID should be null.
			inboundeRx.setRelToMessageId(null);

			inboundeRx.setErxStatusByPatientChkStatus(READY_FOR_AUTOCHECK);

			inboundeRx.setErxStatusByDrugChkStatus(READY_FOR_AUTOCHECK);

			inboundeRx.setErxStatusByProviderChkStatus(READY_FOR_AUTOCHECK);

			inboundeRx.setErxStatusByMessageStatus(READY_FOR_AUTOCHECK);

			inboundeRx.setMessage(incomingMessage);

			inboundeRx.setPharmacyId(0);

			inboundeRx.setCreatedDate(date);

			inboundeRx.setUpdatedDate(date);

			inboundeRx.setReceivedDate(date);

			inboundNcpdpMsgService.saveInboundERx(inboundeRx);

			// To and From are flipped.
			responseBuffer.append(TO_QUALIFIER_D);
			responseBuffer.append(messageFrom);
			responseBuffer.append(TO_TAG_END);
			responseBuffer.append(FROM_QUALIFIER_P);
			responseBuffer.append(messageTo);
			responseBuffer.append(FROM_ENG_TAG);
			responseBuffer.append(MESSAGE_ID_START);
			responseBuffer.append(newMessageID);
			responseBuffer.append(MESSAGE_ID_END);
			responseBuffer.append(RELATES_TO_MESSAGE_ID);
			responseBuffer.append(relatesToMessageID);
			responseBuffer.append(RELATES_TO_MESSAGE_ID_END_TAG);
			responseBuffer.append(SENT_TIME_START);
			responseBuffer.append(sentDateString);
			responseBuffer.append(SENT_TIME_EBD);
			responseBuffer.append(MESSAGE_HEADER_END);
			responseBuffer.append(MESSAGE_BODY_START);
			responseBuffer.append(MESSAGE_STATUS_START);
			responseBuffer.append(CODE_000);
			responseBuffer.append(DESCRIPTION_MESSAGE_RECEVIED_DESCRIPTION);
			responseBuffer.append(MESSAGE_STATUS_END);
			responseBuffer.append(MESSAGE_BODY_END);
			responseBuffer.append(MESSAGE_END);

			response = responseBuffer.toString();

		} catch (SAXException ex) {
			
			LOG.error("Error in InboundNCPDPMessageServiceImpl:" + ex.getMessage());

			// build error response. To and From are flipped.
			
			responseBuffer.append(TO_QUALIFIER_D);
			responseBuffer.append(messageFrom);
			responseBuffer.append(TO_TAG_END);
			responseBuffer.append(FROM_QUALIFIER_P);
			responseBuffer.append(messageTo);
			responseBuffer.append(FROM_ENG_TAG);
			responseBuffer.append(MESSAGE_ID_START);
			responseBuffer.append(newMessageID);
			responseBuffer.append(MESSAGE_ID_END);
			responseBuffer.append(RELATES_TO_MESSAGE_ID);
			responseBuffer.append(relatesToMessageID);
			responseBuffer.append(RELATES_TO_MESSAGE_ID_END_TAG);
			responseBuffer.append(SENT_TIME_START);
			responseBuffer.append(sentDateString);
			responseBuffer.append(SENT_TIME_EBD);
			responseBuffer.append(MESSAGE_HEADER_END);
			responseBuffer.append(MESSAGE_BODY_START);
			responseBuffer.append(MESSAGE_ERROR_START);
			responseBuffer.append(ERROR_CODE_602);
			responseBuffer.append(ERROR_DESCRIPTION_XSD_VALIDATION);
			responseBuffer.append(MESSAGE_ERROR_END);
			responseBuffer.append(MESSAGE_BODY_END);
			responseBuffer.append(MESSAGE_END);

			response = responseBuffer.toString();

		} catch (Exception e) {

			LOG.error("Error in InboundNCPDPMessageServiceImpl:" + e.getMessage());

			// build error response. To and From are flipped.
			
			responseBuffer.append(TO_QUALIFIER_D);
			responseBuffer.append(messageFrom);
			responseBuffer.append(TO_TAG_END);
			responseBuffer.append(FROM_QUALIFIER_P);
			responseBuffer.append(messageTo);
			responseBuffer.append(FROM_ENG_TAG);
			responseBuffer.append(MESSAGE_ID_START);
			responseBuffer.append(newMessageID);
			responseBuffer.append(MESSAGE_ID_END);
			responseBuffer.append(RELATES_TO_MESSAGE_ID);
			responseBuffer.append(relatesToMessageID);
			responseBuffer.append(RELATES_TO_MESSAGE_ID_END_TAG);
			responseBuffer.append(SENT_TIME_START);
			responseBuffer.append(sentDateString);
			responseBuffer.append(SENT_TIME_EBD);
			responseBuffer.append(MESSAGE_HEADER_END);
			responseBuffer.append(MESSAGE_BODY_START);
			responseBuffer.append(MESSAGE_ERROR_START);
			responseBuffer.append(ERROR_CODE_602);
			responseBuffer.append(ERROR_DESCRIPTION_APPLICATION_ERROR);
			responseBuffer.append(MESSAGE_ERROR_END);
			responseBuffer.append(MESSAGE_BODY_END);
			responseBuffer.append(MESSAGE_END);

			response = responseBuffer.toString();

		} finally {

			StreamUtilities.safeClose(inputStream);
			
			inboundeRx = null;
			relatesToMessageID = null;
			messageFrom = null;
			messageTo = null;
			incomingMessageID = null;
			
			xsdSource = null;
			xsdStreamSource = null;
			responseBuffer = null;
		}

		return response;
	}

}
