package gov.va.med.esr.common.batchprocess;

import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKey;
import gov.va.med.esr.service.CommsEmailBulletinService;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.trigger.BulletinTrigger;
import gov.va.med.esr.service.trigger.BulletinTriggerEvent;
import gov.va.med.fw.batchprocess.AbstractDataQueryIncrementalProcess;
import gov.va.med.fw.batchprocess.DataProcessExecutionContext;
import gov.va.med.fw.batchprocess.DataQueryProcessExecutionContext;
import gov.va.med.fw.batchprocess.ProcessStatistics;
import gov.va.med.fw.batchprocess.model.JobResult;
import gov.va.med.fw.persistent.QueryIncrementTracker;
import gov.va.med.fw.service.ServiceException;

import java.math.BigDecimal;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.Validate;


/**
 * A class to process one-time appointment data conversion
 * 
 * @author DNS   ruizc
 *
 */
public class AppointmentConversionProcess extends AbstractDataQueryIncrementalProcess {
	private PersonService personService;
	private String totalCountQueryName = null;	
	private CommsEmailBulletinService commsEmailBulletinService = null;
	protected static final String CURRENT_RECORDS_PROCESSED = "totalRecordsProcessed";	
	private static final String JNAME = "Job-AppointmentConversionProcess";
	private static final String JGROUP = "DEFAULT";	
	private static final String PREFIX_MAX_NUMBER_TO_CONVERT = "-size=";
	private static final String MAX_RECORDS_QUERIED = "maxRecordsQueries";
	protected static final String TOTAL_QUERY_COUNT = "totalQueryCount";	
	
	//CCR 10125: override this method to check if max records queried, if so, return false to stop scrolling
	public boolean continueScrolling(QueryIncrementTracker tracker) {

		DataQueryProcessExecutionContext context = (DataQueryProcessExecutionContext) tracker;
		
		if(context.getContextData().containsKey(MAX_RECORDS_QUERIED))
		{
			Integer maxTotalCount = (Integer) context.getContextData().get(MAX_RECORDS_QUERIED);
			Integer currentRecordsProcessed = (Integer) context.getContextData().get(CURRENT_RECORDS_PROCESSED);
			
			if(currentRecordsProcessed != null && maxTotalCount.intValue() <= currentRecordsProcessed.intValue())
			{
				//reach max (size=nnn), stop scrolling
				return false;
			}
		}
		return !isInterrupted((DataProcessExecutionContext) tracker);
	}
	
	protected ProcessStatistics createProcessStatistics() {
		return new AppointmentConversionStatistics();
	}	
		
	protected void processData(DataQueryProcessExecutionContext context, List acquiredData) {
		if(logger.isInfoEnabled())
			logger.info("Appointment Data Conversion batch process: Query result Size="+(acquiredData == null ? 0 : acquiredData.size()));

		try {
			acquiredData = checkAgainstMax(context, acquiredData);		
		} catch(Exception e) {
			super.throwIllegalStateException("Failed to process", e);	
		}
	
		if (acquiredData == null)
			return;

		// keep track of number of records visited
		Integer currentRecordsProcessed = (Integer) context.getContextData().get(CURRENT_RECORDS_PROCESSED);
		if(currentRecordsProcessed == null)
			currentRecordsProcessed = new Integer(acquiredData.size());
		else
			currentRecordsProcessed = new Integer(currentRecordsProcessed.intValue() + acquiredData.size());
		context.getContextData().put(CURRENT_RECORDS_PROCESSED, currentRecordsProcessed);
					
		AppointmentConversionStatistics stats = (AppointmentConversionStatistics)context.getProcessStatistics();
		
		for (int i = 0; i < acquiredData.size(); i++) {
			BigDecimal personId = (BigDecimal) ((Object [])acquiredData.get(i))[0]; //CCR10125: using scoll			
			try {			
				PersonIdEntityKey key = CommonEntityKeyFactory.createPersonIdEntityKey(personId.toString()); 
				AppointmentConversionResult result = this.getPersonService().processAppointmentConversion(key);
				stats.incrementNumberOfSuccessfulRecords();
				stats.incrementNumberWithCancelledDeclinedStatus(result.getNumberWithCancelledDeclinedStatus());
				stats.incrementNumberWithRejectedEnrollmentStatus(result.getNumberWithRejectedEnrollmentStatus());
					
			} catch (ServiceException ex) {
				context.getProcessStatistics().incrementNumberOfErrorRecords();
				String errMsg = "Error while executing Appointment conversion process for person "
					+ personId;
				context.getExceptionData().add(errMsg+ " Reason: "+ ex.getMessage());
				if(logger.isWarnEnabled())
					logger.warn(errMsg + " Reason: ", ex);
			}
		}
		if(logger.isDebugEnabled()) {
			logger.debug("AppointmentConversionProcess: Processing complete.");
			logger.debug("AppointmentConversionProcess: Successful records count = "+context.getProcessStatistics().getNumberOfSuccessfulRecords());
			logger.debug("AppointmentConversionProcess: Failure records count ="+context.getProcessStatistics().getNumberOfErrorRecords());
		}
		
		/***
		try {
			if (getRemainingCount() == 0) {
				// Must be done so send one-time bulletin				
				this.sendEmailNotification(stats);				
			}
		} catch (ServiceException ex) {
			String errMsg = "Error sending bulletin notification for appointment conversion";
			context.getExceptionData().add(errMsg+ " Reason: "+ ex.getMessage());
			if(logger.isWarnEnabled())
				logger.warn(errMsg + " Reason: ", ex);
		}**/
	}
	
	
	
	protected void handleDataProcessCompleted(DataProcessExecutionContext context) {
		AppointmentConversionStatistics stats = (AppointmentConversionStatistics)context.getProcessStatistics();
		try
		{
			sendEmailNotification(stats);
		} catch (ServiceException ex) {
			String errMsg = "Error sending bulletin notification for appointment conversion";
			context.getExceptionData().add(errMsg+ " Reason: "+ ex.getMessage());
			if(logger.isWarnEnabled())
				logger.warn(errMsg + " Reason: ", ex);
		}
		super.handleDataProcessCompleted(context);
	}

	private int getRemainingCount() throws ServiceException {
		try {
			Number num = getTotalCount();
			return num != null ? num.intValue() : 0;

		} catch (Exception ex) {
			String errMsg = "Error getting total count for appointment conversion. Reason: "+ ex.getMessage();
			throw new ServiceException(errMsg, ex);
		}			
	}
	
	private void sendEmailNotification(AppointmentConversionStatistics stats) throws ServiceException {
		Hashtable dataTab = new Hashtable();
		int totalCancel = 0;
		int totalRejects = 0;

		// include results from this run
		totalCancel += stats.getNumberWithCancelledDeclinedStatus();
		totalRejects += stats.getNumberWithRejectedEnrollmentStatus();

		// get results from previous runs - only ONE e-mail will be sent so need to get total
		List results = this.getBatchProcessService().getFinishedJobResults(JNAME, JGROUP);
		for (Iterator iter = results.iterator(); iter.hasNext();) {
			JobResult result = (JobResult) iter.next();
			String str = result.getStatistics();
			AppointmentConversionStatistics statistics = new AppointmentConversionStatistics();
			try {
				statistics.importFromCSV(str);
				totalCancel += statistics.getNumberWithCancelledDeclinedStatus();
				totalRejects += statistics.getNumberWithRejectedEnrollmentStatus();
				
			} catch (Exception ex) {
				String errMsg = "Error collecting statistics for appointment conversion. Reason: "+ ex.getMessage();
				throw new ServiceException(errMsg, ex);
			}			
		}   					

		// set data for bulletin
		dataTab.put(BulletinTriggerEvent.HecNotifyApplicationConversionComplete.CANCELDECLINE_COUNT, totalCancel +"");
		dataTab.put(BulletinTriggerEvent.HecNotifyApplicationConversionComplete.REJECTED_COUNT, totalRejects +"");

		CommsEmailBulletinService emailSrv = this.getCommsEmailBulletinService();
		emailSrv.sendEmailBulletin(BulletinTrigger.DataType.HEC_NOTIFY_APPLICATION_CONVERSION_COMPLETE,  dataTab, null);
	}	
	
	
	protected List doAcquireData(DataQueryProcessExecutionContext context) throws Exception {
		Object args = context.getExecutionArguments();			
		String singleArg = (String) args;

//!!!! TESTING ONLY - begin		
//		this.setFetchSize(1);
//		this.JDBC_FETCH_SIZE = 1;
//!!!! TESTING ONLY	= end	
		
		// check to see if this "-size=nnn" argument
		if(args != null && singleArg.startsWith(PREFIX_MAX_NUMBER_TO_CONVERT)) {
			// get the number from the singleArg
			String maxNumberToConvert = singleArg.substring(PREFIX_MAX_NUMBER_TO_CONVERT.length());
			
			if (maxNumberToConvert == null || maxNumberToConvert.length()==0)
				return null; //user error, return null
			
			Integer maxNumber = new Integer(maxNumberToConvert);
			
			if (maxNumber.intValue() <=0)
				return null; //user error, return null
			context.getContextData().put(MAX_RECORDS_QUERIED, maxNumber); // custom total count to use
			return super.doAcquireData(context);
		}
		
		return super.doAcquireData(context);
	}	
	
	private List checkAgainstMax(DataQueryProcessExecutionContext context, List acquiredData) throws Exception {
		
		// If the arg is not supplied then we process all the records.
		if ( ! context.getContextData().containsKey(MAX_RECORDS_QUERIED) )
		{
			return acquiredData;
		}
		
		
		//int realizedTotalCount = this.getTotalCount(context);
		int realizedTotalCount = ((Integer) context.getContextData().get(MAX_RECORDS_QUERIED)).intValue();
		
		/**
		if(context.getContextData().containsKey(MAX_RECORDS_QUERIED)) {
			int maxTotalCount = ((Integer) context.getContextData().get(MAX_RECORDS_QUERIED)).intValue();
			if(maxTotalCount < realizedTotalCount) {
				realizedTotalCount = maxTotalCount; 
				context.getContextData().put(TOTAL_QUERY_COUNT, new Integer(maxTotalCount));
			}
		}**/
		
		int currentRecordsQueried = 0;
		if(context.getContextData().containsKey(CURRENT_RECORDS_PROCESSED)) {
			currentRecordsQueried = ((Integer) context.getContextData().get(CURRENT_RECORDS_PROCESSED)).intValue();		
		}
		
		if(currentRecordsQueried >= realizedTotalCount) {
			return null; // already exceeded, no more data for you
		}
		
		if((currentRecordsQueried + acquiredData.size()) > realizedTotalCount) {
			int totalToRemove = (currentRecordsQueried + acquiredData.size()) - realizedTotalCount;
			int lastIndex = acquiredData.size() - 1;
			for(int i=lastIndex; i > (lastIndex - totalToRemove); i--)
				acquiredData.remove(i);
		}

		return acquiredData;
	}	

	
	private final int getTotalCount(DataQueryProcessExecutionContext context) throws Exception {
		Number count = null;
		synchronized(context) {
			count = (Number) context.getContextData().get(TOTAL_QUERY_COUNT);
			if(count == null) {
				count = getTotalCount();
				context.getContextData().put(TOTAL_QUERY_COUNT, count);
			}
		}
		return count.intValue();
	}	
	
	private Number getTotalCount() throws Exception {
		return (Number)this.getDao().find(totalCountQueryName).get(0);

	}	
	
	public void afterPropertiesSet() {
		super.afterPropertiesSet();
		Validate.notNull(personService,	"A PersonService is needed");
	}	



	public PersonService getPersonService() {
		return personService;
	}

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

	public CommsEmailBulletinService getCommsEmailBulletinService() {
		return commsEmailBulletinService;
	}

	public void setCommsEmailBulletinService(
			CommsEmailBulletinService commsEmailBulletinService) {
		this.commsEmailBulletinService = commsEmailBulletinService;
	}

	public String getTotalCountQueryName() {
		return totalCountQueryName;
	}

	public void setTotalCountQueryName(String totalCountQueryName) {
		this.totalCountQueryName = totalCountQueryName;
	}
	
	 
}
