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

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.comms.Correspondence;
import gov.va.med.esr.common.model.lookup.ComLetterTemplateType;
import gov.va.med.esr.common.model.lookup.CorrespondenceStatus;
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.model.person.id.VPIDEntityKey;
import gov.va.med.esr.common.persistent.person.MECPeriodDAO;
import gov.va.med.esr.service.CorrespondenceService;
import gov.va.med.esr.service.PersonMergeService;
import gov.va.med.esr.service.PersonService;
import gov.va.med.fw.batchprocess.AbstractDataQueryIncrementalProcess;
import gov.va.med.fw.batchprocess.DataProcessExecutionContext;
import gov.va.med.fw.batchprocess.DataQueryDetail;
import gov.va.med.fw.batchprocess.DataQueryProcessExecutionContext;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.StringUtils;

/**
 * Batch process to insert pending mail queue entries for Veteran 1095B ACA forms and letters
 *
 * Created 29 Mar 2016
 * @author DNS   faulkj
 *
 * @version 1.0
 */
public class PopulateACAMailQueue extends AbstractDataQueryIncrementalProcess {

	private static final int DEFAULT_JOB_RESULT_UPDATE_INTERVAL = 100;
	private int fetchSize = 500;
	private String calendarYear = null;
	private String startDay = "-01-01";
	private String startDate = null;
	private PersonService personService;
	private PersonMergeService personMergeService;
	private CorrespondenceService correspondenceService;
	private MECPeriodDAO mecPeriodDAO = null;
	private int numRecs = 0;
	private ArrayList<BigDecimal> batch  = null;
	private static final String ERR_DEPRECATED = "Deprecated Person or Pending Merge";
	public static final String ERR_RETRIEVE = "Failed to Retrieve Record";

	protected void executeProcess(DataProcessExecutionContext context) throws Exception {

		//supported custom args is calendar year
		//calendar year to run as current if not identified
		//override calendar year only for the follow up January batches that are meant to catch leftovers of previous year

		String args = (String)context.getExecutionArguments();
		if (!StringUtils.isEmpty(args)) {

			calendarYear = args.trim();

		}

		if (calendarYear == null) {
			Calendar thisYear = Calendar.getInstance();

		    calendarYear = Integer.toString(thisYear.get(Calendar.YEAR));
		}
		startDate = calendarYear + startDay;
		batch = new ArrayList<BigDecimal>();

		DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
		Date date = formatter.parse(startDate);

		Calendar cal = GregorianCalendar.getInstance();
		cal.setTime(date);
		cal.add(Calendar.DAY_OF_YEAR, -1);
		String dateParam = formatter.format(cal.getTime());

		this.setParamNames(new String[] {"startDate"});
		this.setParamValues(new String[] {dateParam});


		super.executeProcess(context);
	}
	@SuppressWarnings("rawtypes")
	@Override
	protected List doAcquireData(DataQueryProcessExecutionContext context) throws Exception {
		List acquiredData = null;
		acquiredData = executeQuery(context);

		return acquiredData;
	}

	@SuppressWarnings("rawtypes")
	@Override
    protected List executeQuery(DataQueryProcessExecutionContext context) throws Exception {
		if (context.isInterrupted()) return null;

		DataQueryDetail currentQuery = context.getCurrentDataQuery();
		List results = getDao().find(currentQuery.getQuery().getQuery(), currentQuery.getQuery().getParamNames(),
        		currentQuery.getQuery().getParamValues(), numRecs, fetchSize, fetchSize);

		numRecs += fetchSize;

        return results;

    }

	//query is a scrolled data set and will execute here for each scroll
	//each batch is a scroll of the data set and will be processed as
	//other data sets are scrolled and processed
	@SuppressWarnings("rawtypes")
	protected void processData(DataQueryProcessExecutionContext context,
			List acquiredData) {

		if (acquiredData == null){
			return;
		}


		ArrayList<BigDecimal> fetchedList = new ArrayList<BigDecimal>();

		for (int i = 0; i < acquiredData.size() && !isInterrupted(context); i++) {
			if (acquiredData.get(i) instanceof Object[]) {
				Object[] row = (Object[]) acquiredData.get(i);
				BigDecimal val = (BigDecimal)row[0];
				batch.add(val);
			} else {
				batch.add((BigDecimal)acquiredData.get(i));
			}
 		}
		fetchedList = new ArrayList<BigDecimal>(batch);
		batch.clear();
		processEntityData(context, fetchedList, calendarYear);
	}

	public void processEntityData(DataQueryProcessExecutionContext context, ArrayList<BigDecimal> fetchedList, String calendarYear) {

		processBatch(context, fetchedList, calendarYear);

	}

	public void processBatch(DataQueryProcessExecutionContext context, ArrayList<BigDecimal> submitBatch, String calendarYear) {
		BigDecimal personId = null;

		try {
			for (int i = 0; i < submitBatch.size(); i++) {

				if (isInterrupted(context)) {
					return;
				}

				Correspondence c = null;
				Person onFile = null;

				if(shouldUpdateJobResult(context))
					this.updateJobResult(context);

				try {
					personId = (BigDecimal)submitBatch.get(i);
					PersonIdEntityKey personKey = CommonEntityKeyFactory.createPersonIdEntityKey(personId);

					VPIDEntityKey vpid = this.getPersonService().getVPIDByPersonId(personKey);

					onFile = this.getPersonService().getPersonWithoutIdentityTraits(vpid);

					if (onFile == null) {
						setRejectAndError(null, personId.toString(), ERR_RETRIEVE, context);
						continue;
					}

					c = new Correspondence(onFile);

					DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
					Date date = formatter.parse(startDate);

			    	c.setEffectiveDate(date);

			    	this.getCorrespondenceService().triggerNewCMSCorrespondence(c, ComLetterTemplateType.FORM_NUMBER_800);
			    	//submit it then reject it
			    	//so that there is a record of it and reason for reject
			    	//instead of just totally skipping over deprecated records like HB does
					if (this.getPersonMergeService().hasDeprecatedRecord(personKey)) {

						setRejectAndError(c, personId.toString(), ERR_DEPRECATED, context);
					}


				} catch (Exception ex) {
					setRejectAndError(c, (personId == null) ? "" : personId.toString(), ex.getMessage(), context);
					continue;
				}

				context.getProcessStatistics().incrementNumberOfSuccessfulRecords();
			}
		} catch (Exception ex) {
			logger.error("Unknown Exception in Populate ACA Mail Queue: " + ex.getMessage());
			context.getProcessStatistics().incrementNumberOfErrorRecords();
		}


	}

	private void setRejectAndError(Correspondence form, String personId, String errMsg, DataProcessExecutionContext context) {
		try {

			if (form != null)
			this.getCorrespondenceService().updateACACorrespondence(form, CorrespondenceStatus.ENROLLMENT_REJECT, errMsg);

			context.getProcessStatistics().incrementNumberOfErrorRecords();

			logger.error("Failed to generate ACA mail queue entry for person: " + personId + ": " + errMsg);

		} catch (ServiceException ex) {
			logger.error("Failed to update ACA correspondence for person: " + personId, ex);
		}
	}


	protected boolean shouldUpdateJobResult(DataQueryProcessExecutionContext context) {
		return context.getProcessStatistics().isTotalNumberMod(DEFAULT_JOB_RESULT_UPDATE_INTERVAL);
	}

	public int getFetchSize() {
		return fetchSize;
	}

	protected void handleDataProcessCompleted(DataProcessExecutionContext  context) {

		//clear out the arg or it will get persisted across executions
		context.setExecutionArguments(null);
		calendarYear = null;

		super.handleDataProcessCompleted(context);

	}

	public void setFetchSize(int fetchSize) {
		this.fetchSize = fetchSize;
	}

	public PersonService getPersonService() {
		return personService;
	}
	public void setPersonService(PersonService personService) {
		this.personService = personService;
	}
	public MECPeriodDAO getMecPeriodDAO() {
		return mecPeriodDAO;
	}
	public void setMecPeriodDAO(MECPeriodDAO mecPeriodDAO) {
		this.mecPeriodDAO = mecPeriodDAO;
	}
	public PersonMergeService getPersonMergeService() {
		return personMergeService;
	}
	public void setPersonMergeService(PersonMergeService personMergeService) {
		this.personMergeService = personMergeService;
	}

	public CorrespondenceService getCorrespondenceService() {
		return correspondenceService;
	}
	public void setCorrespondenceService(CorrespondenceService correspondenceService) {
		this.correspondenceService = correspondenceService;
	}

}
