

package gov.va.med.repositories.fpds.ejb;


import java.nio.ByteBuffer;
import java.util.Map;

import javax.interceptor.Interceptors;
import javax.jms.BytesMessage;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.naming.InitialContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.ejb.interceptor.SpringBeanAutowiringInterceptor;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.jndi.JndiTemplate;
import org.springframework.util.StringUtils;

import gov.va.med.cds.audit.persistence.CdsAuditClobStore;
import gov.va.med.cds.audit.persistence.CdsAuditClobStoreInterface;
import gov.va.med.cds.client.proxy.NetworkClientProxyInterface;
import gov.va.med.cds.hapi.HL7SupportHAPI;
import gov.va.med.cds.hapi.HL7SupportInterface;
import gov.va.med.cds.util.DateTimeUtil;
import gov.va.med.repositories.fpds.ejb.config.BeanConstantsInterface;



@Interceptors(SpringBeanAutowiringInterceptor.class)
public abstract class AbstractFpdsVistAHl7CreateRequestMdb extends AbstractBaseFpdsVistAMdb
implements MessageListener   
{

	protected static String REQUEST_ID = "REQUEST_ID";
    protected static String NO_REQUEST_ID_PROVIDED = "NO_REQUEST_ID";
    protected static String PATIENT_FACILITY = "PATIENT_FACILITY";
    protected static String RECORD_UPDATE_TIME = "RECORD_UPDATE_TIME";
    protected static String TEMPLATE_ID = "<<VISTA_HL7LISTENER_WRITE>>";//For Auditing  - this is not actually a template
		
    protected JndiTemplate jndiTemplate = new JndiTemplate();
	protected String fpdsAppName = BeanConstantsInterface.FPDS_APP_NAME ;
	protected String defaultDomainEnvEntry ="defaultDomainType" ;
	
	@Autowired
	@Qualifier("dnsSocketProxy")
	protected NetworkClientProxyInterface dnsSocketProxy;
	@Autowired
	@Qualifier("persistenceAuditClobStore")
	protected CdsAuditClobStoreInterface persistenceAuditClobStore;
	
    public AbstractFpdsVistAHl7CreateRequestMdb() {
		
	}
     
    protected abstract Map<String, String> getMessageProperties(HL7SupportInterface message) throws JMSException;
    protected abstract String sendHl7Request( String host, String hl7Message );
    protected abstract CdsAuditClobStore buildAuditClobStore(Map<String,String> messageProperties, String hl7Payload);
    protected abstract String buildCommitReject(String messageControlId, String errMsg, HL7SupportInterface hl7Support );
   

    /* (non-Javadoc)
     * @see javax.jms.MessageListener#onMessage(javax.jms.Message)
     */
    public void onMessage( Message message )
    {
    	HL7SupportInterface hl7Support = null;
    	String requestId = null;
        String facilityId = null;    
        String response = null;
        String host = null;
        String responseDestinationJndiName = null;
        Map<String,String> messageProperties = null;
        
        boolean valitionError = false;
        StringBuffer errorValidationMessage = new StringBuffer();
        
        try
        {
            byte[] messageBytes = null;
         
                     		
            if ( message instanceof BytesMessage )
            {
                BytesMessage bytesMsg = ( BytesMessage )message;
                int msgLength = ( int )bytesMsg.getBodyLength();
                messageBytes = new byte[msgLength];
                bytesMsg.readBytes( messageBytes );
            }
            else if ( message instanceof TextMessage )
            {
                TextMessage textMsg = ( TextMessage )message;
                messageBytes = textMsg.getText().getBytes();
            }

            //establish Hl7 payload and configure the hl7Support to assist processing and with acknowledgments/rejects
            ByteBuffer messageBuffer = ByteBuffer.wrap( messageBytes );
            String hl7Payload = new String( messageBuffer.array() );
            hl7Support = HL7SupportHAPI.createTranslationSupport( hl7Payload );
            messageProperties = getMessageProperties(hl7Support);//loading key properties that will be used in auditing and validation
            
            responseDestinationJndiName = hL7Utility.getSendingApplicaionDestination(hl7Support.getSendingApplication());
            facilityId = messageProperties.get( PATIENT_FACILITY );
            host = hl7Support.getReceivingFacility();
            requestId = messageProperties.get( REQUEST_ID );
            
            valitionError = validateMsh(hl7Support, requestId, facilityId, host, messageProperties, valitionError, errorValidationMessage);
            
            //check for validation errors will do a fast fail and will not audit the invalid request or attempt to send to Hl7Listener- 
            if(valitionError){
            	errorValidationMessage.append(" Cannot process JMS Message.");
            	//build error response
            	requestId = (requestId != null) ? requestId : NO_REQUEST_ID_PROVIDED;
                response = buildCommitReject(requestId, errorValidationMessage.toString(), hl7Support);  	
            }
            else{
            	/*
            	 * First audit the write request, then call the Hl7Listener to process the write
            	 * 
            	 * Will audit the Hl7 requests that pass up front validation- but if an error occurs
            	 * when attempting the audit we will not stop processing, we will just log the exception and continue
            	 * sending the request to the Hl7Listener.*/
            	try{
            		CdsAuditClobStore auditClobStore = buildAuditClobStore(messageProperties, hl7Payload);
            		persistenceAuditClobStore.persistCdsAuditClob(auditClobStore);
            	}
            	catch(Exception auditException){
            		//log exception - but we will continue processing even if audit was unsuccessful
            		String msg = "Exception auditing Hl7 ViatA write request.";
            		logger.error( gov.va.med.cds.util.LogMessageUtil.buildMessage( defaultDomainEnvEntry, requestId, fpdsAppName, msg ),auditException );	
            	}
            
            	//call the Hl7Listener
            	response = sendHl7Request(host, hl7Payload);
            }
        }
        catch ( Exception e )
        {
        	String msg = "Exception occured while processing FPDS Hl7 write request: " + e.getMessage();
        	requestId = (requestId != null) ? requestId : NO_REQUEST_ID_PROVIDED;
            response = buildCommitReject(requestId, msg, hl7Support); 
            logger.error( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, requestId, fpdsAppName, msg ), e );           
        }
        finally 
        {
        	hl7Support = HL7SupportHAPI.createTranslationSupport( response );
        	String ackType =hl7Support.getAcknowledgementCode();
        	//return only error commit rejects - ignore commit accepts 'CA'
        	if(response != null && ackType != null && (ackType.equals("CR") || ackType.equals("CE")) )
        	{
        		try {
                       	
        			final String responseMessage = response;
        			final String responseRequestId = requestId;
        		
        			/*This code will NOT check the  'Reply-To' queue associated with this request, because we expect to always send to a known designated client queue
        			and a REPLY-TO queue could be a temporary queue - or a mistake, etc.  We need to able to send these Acks and/or the AppAcks which are sent by a different process 
        			to the same client queue*/
        			Destination defaultDestination = responseJmsTemplate.getDefaultDestination();
        			//get intended client destination
        			InitialContext context = new InitialContext();
        			Destination destination = null;
        			try{
        				destination = (Destination)context.lookup(responseDestinationJndiName);
        			}
        			catch(Exception ee){
        				//The application name in MSH 3 was not correct - the DNS name was not located in configuration will need to attempt sending response to the default Q set above
        				String errMsg = "Failed to resolve the JNDI name for the JMS response destination - could not send ack response: " + responseMessage;
        				logger.error( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, requestId, fpdsAppName ,errMsg ), ee );
        			}
        			        	
        			if(destination != null){
        				try{
        					sendAckToDestination(responseMessage, responseRequestId, destination);
        				}catch(Exception ex1){
        					//can't resolve destination by name - only option is to send it to default destination.
        					String errMsg = "Failed to resolve the response JMS destination - could not send ack response: " + responseMessage;
        	        		logger.error( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, requestId, fpdsAppName, errMsg ), ex1 );
        	        			
        					sendAckToDestination(responseMessage, responseRequestId, defaultDestination);
        				}
                     }
                     else{
                     	sendAckToDestination(responseMessage, responseRequestId, defaultDestination);
                     }
        			
        		} 
        		catch (Exception ex2) {
        			String errMsg = "Failed to publish response message to a JMS destination - could not send ack response: " + response;
        			logger.error( gov.va.med.cds.util.LogMessageUtil.buildMessage( null, requestId, fpdsAppName, errMsg ), ex2 );
        			//unable to send ACK - incoming message does not have response information populated in MSH - will NOT re-throw error because that would place message on the errorQ - we don't want any messages to go to errorQ
        		}
        	}
        }
    }
    

	private boolean validateMsh(HL7SupportInterface hl7Support, String requestId, String facilityId,
			String host, Map<String, String> messageProperties, boolean valitionErrorThrown,
			StringBuffer errorValidationMessage) {
		
		//all validation error messages are concatenated into one validation error message
		
		//MSH 3
		if(StringUtils.isEmpty(hl7Support.getSendingApplication())){
			valitionErrorThrown = true;
			errorValidationMessage.append("Sending Application  (MSH-3-1) is missing from the message. ");
		}
		//MSH 4
		if(StringUtils.isEmpty(hl7Support.getStationNumber()) || StringUtils.isEmpty(hl7Support.getSendingFacility()) || StringUtils.isEmpty(hl7Support.getSendingFacilityType())){
			//cannot determine which component is missing and what component any existing value was intended for - all 3 components are required - will return a generic error message
			valitionErrorThrown = true;
			errorValidationMessage.append("MSH-4 Sending Facility is missing one or more of the 3 required components. ");
		}
		//MSH 5
		if(StringUtils.isEmpty(hl7Support.getReceivingApplication())){
			valitionErrorThrown = true;
			errorValidationMessage.append("Receiving Application (MSH-5-1) is missing from the message. ");
		}
		//MSH 6
		if(StringUtils.isEmpty(facilityId) || StringUtils.isEmpty(host) || StringUtils.isEmpty(hl7Support.getReceivingFacilityType()) ){
			//cannot determine which component is missing and what component any existing value was intended for - all 3 components are required - will return a generic error message
			valitionErrorThrown = true;
			errorValidationMessage.append(" MSH-6 Receiving Facility is missing one or more of the 3 required components. ");
		}
		//MSH 7
		String messageDateTime = messageProperties.get( RECORD_UPDATE_TIME );
		if(StringUtils.isEmpty(messageDateTime)){
			//MSH-7 is missing
			valitionErrorThrown = true;
			errorValidationMessage.append("MessageDateTime (MSH-7-1) is missing from the message. ");
		}	
		else if(!DateTimeUtil.isDateValidHl7Format(messageDateTime)){
			//MSH-7 is present but not in the correct format or incomplete
			valitionErrorThrown = true;
			errorValidationMessage.append("MessageDateTime (MSH-7-1) is an invalid date or is not in the correct date time format 'yyyyMMddHHmmssZ' - " + messageDateTime);
		}	
		//MSH 10
		if(StringUtils.isEmpty(requestId)){
			valitionErrorThrown = true;
			errorValidationMessage.append("MessageControlId (MSH-10)is missing from the message. ");
		}
		return valitionErrorThrown;
	}
     
    public void setResponseJmsTemplate( JmsTemplate responseJmsTemplate )
    {
        this.responseJmsTemplate = responseJmsTemplate;
    }
    
    protected String getCurrentTime(){
    	return DateTimeUtil.getCurrentDateTime("yyyyMMddHHmmssZ");
    }
      
}


