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

//Framework Classes
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;

import gov.va.med.esr.common.model.ee.MilitaryService;
import gov.va.med.esr.common.model.ee.MilitaryServiceSiteRecord;
import gov.va.med.esr.common.model.lookup.FunctionalGroup;
import gov.va.med.esr.common.model.lookup.MessageType;
import gov.va.med.esr.common.model.lookup.ServicePeriod;
import gov.va.med.esr.common.model.messaging.MessageLogEntry;
import gov.va.med.esr.common.model.messaging.SiteIdentity;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKey;
import gov.va.med.esr.common.rule.service.ConsistencyCheckException;
import gov.va.med.esr.common.rule.service.ConsistencyCheckRuleService;
import gov.va.med.esr.service.MilitaryInfoService;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.trigger.FilterSitesPersonTriggerEvent;
import gov.va.med.esr.service.trigger.IncomeYearTriggerEvent;
import gov.va.med.esr.service.trigger.PartialPersonTriggerEvent;
import gov.va.med.esr.service.trigger.PersonTriggerEvent;
import gov.va.med.fw.hl7.BatchMessage;
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.MSH;
import gov.va.med.fw.model.EntityKey;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.builder.BuilderException;
import gov.va.med.fw.util.builder.ValidatorException;
import gov.va.med.fw.validation.ValidationFieldMessage;
import gov.va.med.fw.validation.ValidationMessage;
import gov.va.med.fw.validation.ValidationMessages;

/**
 * Class to process Unsolicited Outbound messages
 * 
 * @author Alex Yoon
 * @version 1.0
 */
public class UnsolicitedOutboundMessageService extends
        AbstractOutboundProcessService implements OutboundMessageService
{
	private static String POS_CC_ERROR_PREFIX = "PERIOD_OF_SERVICE";
	private static final int CALCULATE_POS_RETRY_COUNT = 1;
	
	private ConsistencyCheckRuleService consistencyCheckRuleService;
	private MilitaryInfoService militaryInfoService;
	private PersonService personService;
	
    /**
     * Default constructor.
     */
    public UnsolicitedOutboundMessageService()
    {
        super();
    }

    public void processMessage(Person person, SiteIdentity siteIdentity)
            throws ServiceException
    {
        this.processMessage(person, siteIdentity, null, null);
    }

    /*
     * (non-Javadoc)
     * 
     * @see gov.va.med.esr.messaging.service.outbound.OutboundMessageService#processMessage(gov.va.med.esr.common.model.person.Person,
     *      gov.va.med.esr.common.model.messaging.SiteIdentity,
     *      gov.va.med.fw.service.trigger.TriggerEvent)
     */
    public void processMessage(Person person, SiteIdentity siteIdentity,
            PersonTriggerEvent triggerEvent) throws ServiceException
    {
        this.processMessage(person, siteIdentity, triggerEvent, null);
    }

    /*
     * (non-Javadoc)
     * 
     * @see gov.va.med.esr.messaging.service.outbound.OutboundMessageService#processMessage(gov.va.med.esr.common.model.person.Person,
     *      gov.va.med.esr.common.model.messaging.SiteIdentity,
     *      gov.va.med.fw.service.trigger.TriggerEvent)
     */
    public void processMessage(Person person, SiteIdentity siteIdentity,
            MessageLogEntry log) throws ServiceException
    {
        this.processMessage(person, siteIdentity, null, log);
    }
    
    /**
     * This will build a BatchMessage, Create MessageLogEntries, Persist in
     * Transaction log table and publish the message.
     * 
     * The PartialPersonTriggerEvent created by rules contains EntityKeys
     * indicating those entities which got updated and for which the message
     * needs to be generated. If there are multiple EntityKeys, then the
     * BatchMessage will contain multiple MSH segments each corresponding to the
     * updated Entity.
     * 
     * Note: All the existing unsolicitated outbound are BatchMessages. So we do
     * not have to worry about "single/non-batch" messages.
     * 
     * @param person
     * @param siteIdentity
     * @param triggerEvent
     * @param incomingLogEntry
     * @throws ServiceException
     */
    public void processMessage(Person person, SiteIdentity siteIdentity,
            PersonTriggerEvent triggerEvent, MessageLogEntry incomingLogEntry)
            throws ServiceException
    {
    	 this.processMessage(person, siteIdentity, triggerEvent, incomingLogEntry, false);
    	
    }    

    /**
     * This will build a BatchMessage, Create MessageLogEntries, Persist in
     * Transaction log table and publish the message.
     * 
     * The PartialPersonTriggerEvent created by rules contains EntityKeys
     * indicating those entities which got updated and for which the message
     * needs to be generated. If there are multiple EntityKeys, then the
     * BatchMessage will contain multiple MSH segments each corresponding to the
     * updated Entity.
     * 
     * Note: All the existing unsolicitated outbound are BatchMessages. So we do
     * not have to worry about "single/non-batch" messages.
     * 
     * @param person
     * @param siteIdentity
     * @param triggerEvent
     * @param incomingLogEntry
     * @param isRetransmitFromUI - if it is a retransmit for a single message from UI
     * @throws ServiceException
     */
    public void processMessage(Person person, SiteIdentity siteIdentity,
            PersonTriggerEvent triggerEvent, MessageLogEntry incomingLogEntry, boolean isRetransmitFromUI)
            throws ServiceException
    {    	
    	MessageType.Code messageTypeCode = MessageType.Code.getByCode(getMessageTypeCode());
    	
        try
        {
            BatchMessage batchMessage = buildBatchMessage(person, siteIdentity,
                    triggerEvent);

            MessageLogEntry logEntry;

            List messages = batchMessage.getMessages();
            boolean failedConsistencyChecks = false;
            for (Iterator iter = messages.iterator(); iter.hasNext();)
            {
                Message message = (Message) iter.next();
                
                // process the consistency checks for this Message
            	if(!processConsistencyChecks(person, siteIdentity, triggerEvent, message, messageTypeCode, 0)) {
            		failedConsistencyChecks = true;
            		continue; // do nothing else with this Message
            	}
                
                //If normal outbound message.
                if (incomingLogEntry == null)
                {
                    logEntry = super.createMessageLog(message, batchMessage
                            .getMessageID(), message.getMessageID(), person,
                            siteIdentity.getVaFacility(), super
                                    .buildTransmissionDate(message),
                                    getRetransmissionInfo(messageTypeCode,triggerEvent));
                } else
                {//If retransmission. There will be only one message as there
                 // is only one LogEntry.
                    logEntry = super.createMessageLog(message, batchMessage
                            .getMessageID(), message.getMessageID(), person,
                            siteIdentity.getVaFacility(), super
                                    .buildTransmissionDate(batchMessage),
                            isRetransmitFromUI ? 0 : incomingLogEntry.getRetransmissionCount() + 1,
                            incomingLogEntry);
                }

                super.logMessage(logEntry);

            }

            // only send batch message if there were no issues with any of its messages
            if(!failedConsistencyChecks) {	
	            super.publishMessage(batchMessage);
	
	            if(logger.isInfoEnabled()) {
		            super.logger.info("Built Unsolicitated Outbound Batch Message Id "
		                    + batchMessage.getMessageID() + " and message type "
		                    + this.getMessageTypeCode() + " for facility " + siteIdentity.getVaFacility().getCode()); 
	            }
	            
	            if (super.logger.isDebugEnabled())
	            {
	                super.logger.debug("Batch Message: "
	                        + batchMessage.getMessageData());
	            }
            }
        } catch (BuilderException e)
        {
            if(e.getCause() instanceof ValidatorException) {
            	// this should case Consistency Check handling to kick in...            	
            	createConsistencyCheckWorkloadCase((PersonIdEntityKey) person.getPersonEntityKey(), siteIdentity, messageTypeCode,
            			FunctionalGroup.EE,  e.getCause().getMessage());
            } else {
                throw new ServiceException("Failed to build outbound "
                        + this.getMessageTypeCode()
                        + " message due to an exception in processMessage", e);            	
            }
        } catch (InvalidMessageException e)
        {
            //ASSERT: Should not happen if the message is already parsed.
            throw new RuntimeException(
                    "Failed to obtain data from the message", e);
        }
    }

    //Can be overridden by subclasses
    protected String getRetransmissionInfo(Object obj)
    {    	
        return null;
    }

    /**
     * 
     * @param messageTypeCode
     * @param triggerEvent
     * @return
     */
    protected String getRetransmissionInfo(MessageType.Code messageTypeCode,PersonTriggerEvent triggerEvent)
    {
    	String retransmissionInfo=null;
    	if(messageTypeCode!=null && MessageType.CODE_ORUZ06_TO_SITE.getName().equals(messageTypeCode.getName()) && triggerEvent!=null)
    	{
    		IncomeYearTriggerEvent incomeYearEvent = (IncomeYearTriggerEvent) triggerEvent;
    		retransmissionInfo= incomeYearEvent.getIncomeYear()!=null?incomeYearEvent.getIncomeYear().toString():null;
    	}
        return retransmissionInfo;
    }

    //This is done only for ORUZ11-S outbound message
    private boolean recalculatePOS(Person person, SiteIdentity siteIdentity, Message message) throws ServiceException {
    	boolean isSuccess = false;
    	if (getMilitaryInfoService() != null) {
    		if (person != null) {
    	        MilitaryService militaryService = person.getMilitaryService();

    	        MilitaryServiceSiteRecord hecSite = militaryService.getHECMilitaryServiceSiteRecord();
    	        ServicePeriod servicePeriod = (hecSite != null) ? hecSite.getServicePeriod() : null;
    	        if(servicePeriod != null) {
    	            String posCode = servicePeriod.getCode();
    	            logger.info("Current POS prior to recalculatePOS is "+posCode+":"+servicePeriod.getName()+servicePeriod.getDescription());
    	            if (StringUtils.isNotEmpty(posCode)) {
    	            }
    	        }   			
    			
    		}    		
    		person = getMilitaryInfoService().updateCalculatePeriodOfService(person);
    		
    		if (person != null) {
    			
    	        MilitaryService militaryService = person.getMilitaryService();

    	        MilitaryServiceSiteRecord hecSite = militaryService.getHECMilitaryServiceSiteRecord();
    	        ServicePeriod servicePeriod = (hecSite != null) ? hecSite.getServicePeriod() : null;
    	        if(servicePeriod != null) {
    	            String posCode = servicePeriod.getCode();
    	            logger.info("Recalculated POS is "+posCode+":"+servicePeriod.getName()+servicePeriod.getDescription());
    	            if (StringUtils.isNotEmpty(posCode)) {
    	            	isSuccess = true;
    	            	try {
    	            		if (message != null && message.getSegment(SegmentConstants.ZSP) != null) {
    	            			((gov.va.med.fw.hl7.segment.ZSP)message.getSegment(SegmentConstants.ZSP)).setServicePeriod(posCode);
    	            		}
						} catch (InvalidMessageException e) {
							// TODO Auto-generated catch block
							//e.printStackTrace();
							logger.error("Error in updating service period in outbound ORUZ11-S message "+e);
						}
    	            	if (getPersonService() != null) {
    	            		getPersonService().save(person);
    	            	}
    	            }
    	        }   			
    			
    		}
    	}
    	return isSuccess;
    }
    
    private boolean processConsistencyChecks(Person person, SiteIdentity siteIdentity, PersonTriggerEvent triggerEvent, Message message, MessageType.Code messageType, int retryTimes) throws ServiceException {
    	boolean sendOutboundMessage = true;
    	
    	if(this.consistencyCheckRuleService != null) {
    		try {
    			consistencyCheckRuleService.processConsistencyChecksForMessage(person, triggerEvent, message, messageType);
    		} catch(ConsistencyCheckException e) {
                PersonIdEntityKey personKey = (PersonIdEntityKey) person.getPersonEntityKey();
                // Defect 6332 fix
                if(e.isBlocking()) {
                    sendOutboundMessage = false;
                    if(logger.isErrorEnabled())
                        logger.error("Unable to send outbound message [" + messageType.getCode() +
                                "] due to FAILED consistency checks for Person Id [" +
                                personKey.getPersonId() + "]");
                }
                else {
                    if(logger.isWarnEnabled())
                        logger.warn("Sending outbound message [" + messageType.getCode() +
                                "] for non-blocking FAILED consistency checks for Person Id [" +
                                personKey.getPersonId() + "]");                 
                }
                
    			if(siteIdentity.getGroup() == null || !siteIdentity.getGroup().isConsistencyCheckCaseCreated()) {
    				
	    			// current thinking is only blocking CC will create a WorkloadCase
	    			if(e.isBlocking()) {
						// before creating a case, need to determine if we should create an EE case or a DQ case or both
						StringBuffer eeCaseErrorMessages = new StringBuffer();
						StringBuffer dqCaseErrorMessages = new StringBuffer();
						//CCR12878 - Fixed issue of not creating workload cases if msg is missing context info.
						StringBuffer defaultCaseErrorMessages = new StringBuffer();
						ValidationMessages msgs = e.getValidationMessages();
						Iterator itr = msgs.get();
						ValidationFieldMessage msg = null;
						while(itr.hasNext()) {
							msg = (ValidationFieldMessage) itr.next();
							
							// CCR 10399 -- added createWklist flag to allow more flexibility 
							// where a blocking CC does NOT create a WorkloadCase
							if (msg.isCreateWkList()) {
								//ESR 4.2.1_CodeCR????? - ES is not Calculating POS on any records
								if (msg.getKey() != null && msg.getKey().contains(POS_CC_ERROR_PREFIX) && MessageType.CODE_ORUZ11_TO_SITE.getCode().equals(messageType.getCode()) 
										&& retryTimes < CALCULATE_POS_RETRY_COUNT) {
									//Calculate POS
									boolean isSuccess = recalculatePOS(person, siteIdentity, message);
									retryTimes += 1;
									sendOutboundMessage = processConsistencyChecks(person, siteIdentity, triggerEvent, message, messageType, retryTimes);
									return sendOutboundMessage;
								}
								
								if(FunctionalGroup.EE.getCode().equals(msg.getContextInfo())) {
									eeCaseErrorMessages.append(msg.getKey()).append(", ");
								}
								else if(FunctionalGroup.DQ.getCode().equals(msg.getContextInfo())) {
									dqCaseErrorMessages.append(msg.getKey()).append(", ");
								} else {
									//CCR12878 - Fixed issue of not creating workload cases if msg is missing context info.
									defaultCaseErrorMessages.append(msg.getKey()).append(", ");
								}
							}
						}
	    				if(siteIdentity.getGroup() != null) {
		    				// this ensures only one ConsistencyCheck case is created across the group
		    				siteIdentity.getGroup().setConsistencyCheckCaseCreated(true);
	    				}
	    				
		    			// create CC case(s)
						if(dqCaseErrorMessages.length() > 0) {
							createConsistencyCheckWorkloadCase(personKey, siteIdentity, messageType, FunctionalGroup.DQ, dqCaseErrorMessages.toString());
						}
						if(eeCaseErrorMessages.length() > 0) {
							createConsistencyCheckWorkloadCase(personKey, siteIdentity, messageType, FunctionalGroup.EE, eeCaseErrorMessages.toString());
						}
						//CCR12878 - Fixed issue of not creating workload cases if msg is missing context info.
						if(defaultCaseErrorMessages.length() > 0) {
							createConsistencyCheckWorkloadCase(personKey, siteIdentity, messageType, FunctionalGroup.EE, defaultCaseErrorMessages.toString());
						}
	    			} 
    			}
    		}
    	}
    	return sendOutboundMessage;
    }
    
    private void createConsistencyCheckWorkloadCase(PersonIdEntityKey key, SiteIdentity siteIdentity, MessageType.Code messageTypeCode,
    		FunctionalGroup.Code groupType, String ccErrorMsgs) throws ServiceException {
    	getMessagingWorkloadCaseHelper().createWorkloadCaseForConsistencyCheck(key, siteIdentity, messageTypeCode, groupType, ccErrorMsgs);
    }    
    private BatchMessage buildBatchMessage(Person person,
            SiteIdentity siteIdentity, PersonTriggerEvent triggerEvent)
            throws ServiceException
    {
        BatchMessage batchMessage = null;

        try
        {
            Message message = null;

            if (triggerEvent != null
                    && triggerEvent instanceof PartialPersonTriggerEvent)
            {
                Collection entityKeys = getEntityKeys((PartialPersonTriggerEvent)triggerEvent);

                if (entityKeys == null || entityKeys.size() == 0)
                {
                    message = super.buildMessage(new Object[] { person,
                            siteIdentity, null, triggerEvent });
                    batchMessage = this.buildBatch(new Message[] { message });

                } else
                {
                    Message[] messages = new Message[entityKeys.size()];
                    int messageNo = 0;

                    //If there are multiple EntityKeys, then the BatchMessage
                    // will
                    // contain multiple
                    //MSH segments each corresponding to the updated Entity.
                    for (Iterator iter = entityKeys.iterator(); iter.hasNext();)
                    {
                        EntityKey entityKey = (EntityKey) iter.next();

                        message = super.buildMessage(new Object[] { person,
                                siteIdentity, entityKey, triggerEvent });

                        messages[messageNo] = message;

                        messageNo++;
                    }

                    //Build the batch message containing multiple messages(MSH
                    // segments)
                    batchMessage = this.buildBatch(messages);
                }

            } else
            {//If not from a PartialPersonTriggerEvent,
                message = super.buildMessage(new Object[] { person,
                        siteIdentity, null, triggerEvent });
                batchMessage = this.buildBatch(new Message[] { message });
            }

        } catch (BuilderException e)
        {
            throw new ServiceException("Failed to build outbound "
                    + this.getMessageTypeCode()
                    + " message due to an exception", e);
        }

        return batchMessage;
    }
    
    private Collection getEntityKeys(PartialPersonTriggerEvent triggerEvent)
    {
        
        if(triggerEvent instanceof FilterSitesPersonTriggerEvent)
        {
            FilterSitesPersonTriggerEvent event = (FilterSitesPersonTriggerEvent)triggerEvent; 
            return event.getEntityKeysAndSites() == null ? null : event.getEntityKeysAndSites().keySet();
        }else{
            return triggerEvent.getEntityKeys();
        }
        
    }

	/**
	 * @return Returns the consistencyCheckRuleService.
	 */
	public ConsistencyCheckRuleService getConsistencyCheckRuleService() {
		return consistencyCheckRuleService;
	}

	/**
	 * @param consistencyCheckRuleService The consistencyCheckRuleService to set.
	 */
	public void setConsistencyCheckRuleService(
			ConsistencyCheckRuleService consistencyCheckRuleService) {
		this.consistencyCheckRuleService = consistencyCheckRuleService;
	}
	
    public MilitaryInfoService getMilitaryInfoService()
    {
        return militaryInfoService;
    }

    public void setMilitaryInfoService(MilitaryInfoService militaryInfoService)
    {
        this.militaryInfoService = militaryInfoService;
    }
    
    public PersonService getPersonService()
    {
        return personService;
    }

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