/*******************************************************************************
 * Copyright © 2004 EDS. All rights reserved
 ******************************************************************************/
package gov.va.med.esr.messaging.service.inbound;

// Java Classes
import java.math.BigDecimal;
import java.util.Date;

import gov.va.med.fw.hl7.HL7Message;
import gov.va.med.fw.hl7.HL7MessageUtils;
import gov.va.med.fw.hl7.InvalidMessageException;
import gov.va.med.fw.hl7.Message;
import gov.va.med.fw.hl7.constants.SegmentConstants;
import gov.va.med.fw.hl7.segment.PID;
import gov.va.med.fw.hl7.segment.ZEL;
import gov.va.med.fw.persistent.DAOException;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.StopWatchLogger;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.util.builder.BuilderException;

import gov.va.med.esr.common.builder.entity.ImpreciseDateBuilder;
import gov.va.med.esr.common.infra.ImpreciseDate;
import gov.va.med.esr.common.model.ee.EligibilityVerification;
import gov.va.med.esr.common.model.ee.VerificationInfo;
import gov.va.med.esr.common.model.lookup.EligibilityStatus;
import gov.va.med.esr.common.model.lookup.MessageType;
import gov.va.med.esr.common.model.lookup.VAFacility;
import gov.va.med.esr.common.model.messaging.MessageLogEntry;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.workload.WorkflowCaseInfo;
import gov.va.med.esr.common.persistent.messaging.MessageLogEntryDAO;

import gov.va.med.esr.service.PersonService;

/**
 * Class to process ORUZ07 message.
 * 
 * @author Vu Le
 * @version 1.0
 */
public class Z07ProcessService extends AbstractInboundProcessService {

    private static final String ORUZ07 = "ORUZ07";
    private static final String ORFZ07 = "ORFZ07";
   
   
    /**
	 * An instance of messageLogEntryDAO
	 */
	private MessageLogEntryDAO messageLogEntryDAO = null;

	private PersonService personService;
	
	/**
	 * Default constructor.
	 */
	public Z07ProcessService() {
		super();
	}

	/**
	 * @see gov.va.med.esr.messaging.service.inbound.AbstractInboundProcessService#doProcessMessage(gov.va.med.esr.common.model.person.Person, gov.va.med.fw.hl7.Message, java.lang.String)
	 */
	protected final MessageLogEntry doProcessMessage( Person person,
			                                            Message message, 
                                                     String formattedBody )
	   throws InboundProcessException {
		
      if (person == null) {
			throw super.createInboundProcessException( message, 
                                                    formattedBody,
                                                    null, 
                                                    "AE: Could not find the person", 
                                                    null );
		}
		try {
			return this.doProcessMessage( person, 
                                       message, 
                                       formattedBody, 
                                       super.getReferencedMessageLogEntry( message ) );
		} 
      catch( InboundProcessException e ) {
			throw super.createInboundProcessException( message, 
                                                    formattedBody,
                                                    person, 
                                                    e.getMessage(), 
                                                    e );
		} 
      catch (ServiceException e) {
			throw super.createInboundProcessException( message, 
                                                    formattedBody,
                                                    person, 
                                                    e.getMessage(), 
                                                    e );
		}
	}
	
	/**
	 * @param onFile
	 * @param message
	 * @param formattedBody
	 * @param initiatingMessage
	 * @return
	 * @throws InboundProcessException
	 */
	private final MessageLogEntry doProcessMessage( Person onFile,
                                                   Message message, 
                                                   String formattedBody, 
                                                   MessageLogEntry initiatingMessage ) 
	   throws InboundProcessException {
		
		try {
			// Determine whether this Z07 is a site data record based on
			// an eligibility status coming from a site message and the 
			// current eligibility on file.  A site record is a message log entry
			// in a HL7_TRANSACTION_LOG table with a SITE_RECORD_ELGBTY_STATUS_CODE 
			// column populated to non-null value (V,P,R)
			MessageType msgType = initiatingMessage != null ? initiatingMessage.getType() : null;
			String msgCode = msgType != null ? msgType.getCode() : null;

			// If this is a new Z07, initiatingMessage will be null so use the message type 

			//CCR# 8846: added ORFZ07
			String siteDataStatus = null;
			if( MessageType.CODE_ORUZ07_TO_ESR.getName().equals( msgCode ) || 
					ORUZ07.equals( message.getType() ) ||
					MessageType.CODE_ORFZ07_TO_ESR.getName().equals( msgCode ) || 
					ORFZ07.equals( message.getType() ) ) {

				siteDataStatus = determineSiteDataStatus( onFile, message, msgCode );
			}

			// build a person from incoming message
			Person incoming = buildPerson( onFile, message );

			//Fix RTC Defect # 286516 - ESR not sending Address in 200ESR Add Correlation from new registration Z07
			//Add ESR 200 Correlation to PSIM along with address i.e. delay it till address is populated in incoming person
			onFile = getPersonService().addESRCorrelation(onFile, incoming);
			
			// Create a message log entry for this Z07 message
			MessageLogEntry mle = super.createMessageLog( message,
					formattedBody, 
					incoming, 
					initiatingMessage );
			mle.setSiteReceivedEligibilityStatus( siteDataStatus );

			VAFacility sendingFacility = super.getSendingFacility(message);

			// Z07 message doesn't have an entitlement code so set it to null
			VerificationInfo info = new VerificationInfo( getMessageTypeCode(),
					HL7MessageUtils.getErrorText(message));
			// Set the site number to sending facility
			info.setSiteNumber( sendingFacility.getStationNumber() );

			//Set the DFN in the Verification Info
			String dfn = getDfnFromPID((PID)message.getSegment(SegmentConstants.PID));
			info.setDfn(dfn);

			//set for identifying seeding entry change message source
			info.setMessageControlId(message.getMessageID());

			// create workflow case object for potential usage by Rules
			WorkflowCaseInfo caseInfo = new WorkflowCaseInfo();
			caseInfo.setTransmissionSite(getLookupService().getVaFacilityByStationNumber(message.getSendingFacility()));
			caseInfo.setMessageID(message.getMessageID());

			// create workflow case object for potential usage by Rules
			info.setWorkflowCaseInfo( caseInfo );						

			getMessagingService().processZ07( incoming, sendingFacility, info );
			mle.setPersonId( (BigDecimal)incoming.getEntityKey().getKeyValue() );

			// set the result in a message log entry
			return mle;
		}
		catch( BuilderException e ) {
			throw super.createInboundProcessException( message, 
					formattedBody,
					onFile, 
					e.getMessage(), 
					e );
		}
		catch( Exception e ) {
			throw super.createInboundProcessException( message, 
					formattedBody,
					onFile, 
					e.getMessage(), 
					e );
		}
	}
  
	/**
	 * @return Returns the personService.
	public PersonService getPersonService() {
		return personService;
	}

	public void setPersonService(PersonService personService) {
		this.personService = personService;
	}
    */

	/**
	 * @return Returns the messageLogEntryDAO.
	 */
	public MessageLogEntryDAO getMessageLogEntryDAO() {
		return messageLogEntryDAO;
	}

	/**
	 * @param messageLogEntryDAO The messageLogEntryDAO to set.
	 */
	public void setMessageLogEntryDAO(MessageLogEntryDAO messageLogEntryDAO) {
		this.messageLogEntryDAO = messageLogEntryDAO;
	}

	/**
	 * @param entry
	 * @return
	 * @throws ServiceException
	 */
	private String determineSiteDataStatus( Person onFile, Message incoming, String msgCode ) 
      throws InvalidMessageException, DAOException {
		
		/**
		 * Following are additional logics to update a received eligibility status column
		 * in a HL7_TRANSACTION_LOG table.  The following logics are intented to determine
		 * which Z07 message is considered a valid site data. - VL
		 *
		 * For a new veteran to ESR, capture the eligibility status (on the ZEL segment) 
		 * is Pending Verification, Pending Reverification or Verified.  This message
		 * log entry will be most recent site record to be used for verification, 
		 * if needed by Process VBA.
		 * For each subsequent Z07 message received, you apply the following rules 
		 * only when the veteran's current eligibility status on file in ESR is not 
		 * equal to Verified.  Otherwise, do not update the received eligibility status
		 * of the most recent site record.
		 * 
		 * If the eligibility status of the most recent site record is Pending Verification 
		 * or Pending Reverification, and the new Z07 has an eligibility status of Verified, 
		 * then replace the message id of the most recent site record.
		 *
		 * If the eligibility status of the most recent site record is Pending Verification 
		 * or Pending Reverification, and the new Z07 has an eligibility status of Pending 
		 * Verification or Pending Reverification, and the eligibility status date from the 
		 * Z07 message is more recent than the eligibility status date of the most recent 
		 * site record, then replace the message id of the most recent site record.
		 * 
		 * If the eligibility status of the most recent site record is Verified, and the new 
		 * Z07 has an eligibility status of Verified, and the eligibility status date from 
		 * the Z07 message is more recent than the eligibility status date of the most recent 
		 * site record, then replace the message id of the most recent site record.
		 *
		 *	Otherwise, do not update the message id of the most recent site record.
		 */
      
      StopWatchLogger watch = null;
      if( logger.isDebugEnabled() ) {
         watch = new StopWatchLogger("Z07ProcessService.determineSiteDataStatus");
         if (watch != null) {
        	 watch.start();
         }
      }
		
      // Get the received eligibility status from an incoming message
      HL7Message siteMessage = new HL7Message( incoming );
      
      String incomingStatus = siteMessage.getZELSegment().getEligibilityStatus();
      
      // It is possible for a site to send in a Z07 with an eligibility status of null or empty.  If so, set to Pending
      incomingStatus = StringUtils.isEmpty( incomingStatus ) ? EligibilityStatus.CODE_PENDING_VERIFICATION.getCode() : incomingStatus;

      // Get the received eligibility status date from incoming message
      ZEL zelSegment = siteMessage.getZELSegment();
      String date = zelSegment != null ? zelSegment.getEligibilityStatusDate() : null;
      ImpreciseDateBuilder dateBuilder = new ImpreciseDateBuilder();

      ImpreciseDate impreciseDate = null;
      try {
         impreciseDate = dateBuilder.build( date );
      }
      catch( BuilderException e ) {
         if( logger.isDebugEnabled() ) {
            logger.debug( "Failed to parse an eligibility status date: " + date );
         }
      }
      Date incomingDate = impreciseDate != null ? impreciseDate.getDate() : null;
      
      // Only update if the current person on file is not verified
      EligibilityVerification verification = (onFile != null) ? onFile.getEligibilityVerification() : null;
      EligibilityStatus status = (verification != null) ? verification.getEligibilityStatus() : null;
      String onFileStatus = (status != null) ? status.getCode() : null;

      if( !EligibilityStatus.CODE_VERIFIED.getName().equals( onFileStatus ) ) {

         boolean processed = false;
         
         // If a person doesn't have any enrollment record
         // it means that this is the first time ESR processes this person
         if( onFile.getEnrollmentDetermination() == null ) {
            processed = true;
         }
         else {
            // Get a message log entry dao to query a site data
            MessageLogEntryDAO dao = this.getMessageLogEntryDAO();

            /**
             * If the eligibility status of the most recent site record is Pending Verification 
             * and the new Z07 has an eligibility status of Verified, then replace most recent 
             * site record.
             *
             * If the eligibility status of the most recent site record is Pending Verification 
             * and the new Z07 has an eligibility status of Pending Verification and the eligibility 
             * status date from the Z07 message is more recent than the eligibility status date of 
             * the most recent site record, then replace the message id of the most recent site record.
             */

            // Get the most recent site record of pending verification and unset its site 
            // data status if the incoming status is either verified or pending verification with a more recent date
            String siteStatus = EligibilityStatus.CODE_PENDING_VERIFICATION.getName();
            MessageLogEntry mle = dao.findByReceivedEligiblityStatus( onFile, siteStatus );
            
            if( mle != null && EligibilityStatus.CODE_VERIFIED.getName().equals( incomingStatus ) ) {
               mle.setSiteReceivedEligibilityStatus( null );
               processed= true;
            }
            else if( mle != null && EligibilityStatus.CODE_PENDING_VERIFICATION.getName().equals( incomingStatus ) ) {
               HL7Message pendingMessage = new HL7Message( mle.getBody() );
               zelSegment = pendingMessage.getZELSegment();
               date = zelSegment != null ? zelSegment.getEligibilityStatusDate() : null;

               try {
                  impreciseDate = dateBuilder.build( date );
               }
               catch( BuilderException e ) {
                  
                  if( logger.isDebugEnabled() ) {
                     logger.debug( "Failed to parse an eligibility status date: " + date );
                  }
               }
               Date currentDate = impreciseDate != null ? impreciseDate.getDate() : null;
               if( incomingDate != null && currentDate != null && incomingDate.after( currentDate ) ) {
                  mle.setSiteReceivedEligibilityStatus( null );
               }
               processed= true;
            }
            
            /**
             * If the eligibility status of the most recent site record is Pending Reverification 
             * and the new Z07 has an eligibility status of Verified, then replace most recent 
             * site record.
             *
             * If the eligibility status of the most recent site record is Pending Reverification 
             * and the new Z07 has an eligibility status of Pending Reverification and the eligibility 
             * status date from the Z07 message is more recent than the eligibility status date of 
             * the most recent site record, then replace the message id of the most recent site record.
             */
            if( !processed ) {

               // Get the most recent site record of pending reverification and unset its site 
               // data status if the incoming status is either verified or pending reverification with a more recent date
               siteStatus = EligibilityStatus.CODE_PENDING_REVERIFICATION.getName();
               mle = dao.findByReceivedEligiblityStatus( onFile, siteStatus );
               
               if( mle != null && EligibilityStatus.CODE_VERIFIED.getName().equals( incomingStatus ) ) {
                  mle.setSiteReceivedEligibilityStatus( null );
                  processed= true;
               }
               else if( mle != null && EligibilityStatus.CODE_PENDING_REVERIFICATION.getName().equals( incomingStatus ) ) {
                  HL7Message pendingMessage = new HL7Message( mle.getBody() );
                  zelSegment = pendingMessage.getZELSegment();
                  date = zelSegment != null ? zelSegment.getEligibilityStatusDate() : null;
                  try {
                     impreciseDate = dateBuilder.build( date );
                  }
                  catch( BuilderException e ) {
                     
                     if( logger.isDebugEnabled() ) {
                        logger.debug( "Failed to parse an eligibility status date: " + date );
                     }
                  }
                  Date currentDate = impreciseDate != null ? impreciseDate.getDate() : null;
                  if( incomingDate != null && currentDate != null && incomingDate.after( currentDate ) ) {
                     mle.setSiteReceivedEligibilityStatus( null );
                  }
                  processed= true;
               }
            }
            
            /**
             * If the eligibility status of the most recent site record is Verified, and the new 
             * Z07 has an eligibility status of Verified, and the eligibility status date from 
             * the Z07 message is more recent than the eligibility status date of the most recent 
             * site record, then replace the message id of the most recent site record.
             */
            if( !processed ) {
               // Get the most recent site record of VERIFIED and unset its site 
               // data status if the incoming status is more recent
               siteStatus = EligibilityStatus.CODE_VERIFIED.getName();
               mle = dao.findByReceivedEligiblityStatus( onFile, siteStatus );
               
               if( mle != null && EligibilityStatus.CODE_VERIFIED.getName().equals( incomingStatus ) ) {

                  HL7Message pendingMessage = new HL7Message( mle.getBody() );
                  zelSegment = pendingMessage.getZELSegment();
                  date = zelSegment != null ? zelSegment.getEligibilityStatusDate() : null;
                  try {
                     impreciseDate = dateBuilder.build( date );
                  }
                  catch( BuilderException e ) {
                     
                     if( logger.isDebugEnabled() ) {
                        logger.debug( "Failed to parse an eligibility status date: " + date );
                     }
                  }
                  Date currentDate = impreciseDate != null ? impreciseDate.getDate() : null;
                  if( incomingDate != null && currentDate != null && incomingDate.after( currentDate ) ) {
                     mle.setSiteReceivedEligibilityStatus( null );
                  }
                  processed= true;
               }
            }
         }
      }
      
      if( logger.isDebugEnabled() && watch != null) {
          watch.stopAndLog();
       }
      
		return incomingStatus;
	}
	
	/**
	 * @return Returns the personService.
	 */
	public PersonService getPersonService() {
		return personService;
	}

	/**
	 * @param personService The personService to set.
	 */
	public void setPersonService(PersonService personService) {
		this.personService = personService;
	}	
}