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

import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;

import gov.va.med.esr.common.infra.ImpreciseDate;
import gov.va.med.esr.common.model.CommonEntityKeyFactory;
import gov.va.med.esr.common.model.ee.IRS1095B;
import gov.va.med.esr.common.model.ee.MECPeriod;
import gov.va.med.esr.common.model.lookup.NameType;
import gov.va.med.esr.common.model.lookup.SSNType;
import gov.va.med.esr.common.model.person.BirthRecord;
import gov.va.med.esr.common.model.person.Name;
import gov.va.med.esr.common.model.person.Person;
import gov.va.med.esr.common.model.person.PersonTraits;
import gov.va.med.esr.common.model.person.SSN;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKey;
import gov.va.med.esr.common.model.person.id.PersonIdEntityKeyImpl;
import gov.va.med.esr.common.model.person.id.VPIDEntityKey;
import gov.va.med.esr.common.persistent.person.MECPeriodDAO;
import gov.va.med.esr.common.persistent.person.PersonTraitsDAO;
import gov.va.med.esr.service.IRSTransmissionService;
import gov.va.med.esr.service.LookupService;
import gov.va.med.esr.service.PersonIdentityTraits;
import gov.va.med.esr.service.PersonMergeService;
import gov.va.med.esr.service.PersonService;
import gov.va.med.esr.service.SystemParameterService;
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.persistent.DAOOperations;
import gov.va.med.fw.persistent.QueryInfo;
import gov.va.med.fw.service.ServiceException;
import gov.va.med.fw.util.StringUtils;
import gov.va.med.fw.util.ThreadPool;

/**
 * Batch process to transmit Veteran 1095B ACA coverage data to the IRS for previous calendar year
 *
 * WI# 218485
 * Create 23 Apr 2016
 * @author DNS   faulkj
 *
 * @version 1.0
 */
public class GenerateIRSTransmissionParallelProcess extends AbstractDataQueryIncrementalProcess {

	private static final int DEFAULT_THREAD_POOL_SIZE = 1;
	private static final int DEFAULT_FETCH_SIZE = 110;
	private static final int DEFAULT_JOB_RESULT_UPDATE_INTERVAL = 100;
	public static final String CONTEXT_THREAD_CREATOR = "threadCreator";
	public static final String CONTEXT_TASK_COUNT = "taskCount";
	public static final String CONTEXT_THREAD_POOL = "threadPool";
	public static final String TRANSMISSION_SIZE="IRS Transmission Size";
	private SystemParameterService systemParameterService = null;
	int maxBatchSize;
	private int threadPoolSize = DEFAULT_THREAD_POOL_SIZE;
	private int fetchSize = DEFAULT_FETCH_SIZE;
	private String spawnedTaskId;
	private String calendarYear = null;
	private String startDay = "-01-01";
	private ArrayList<BigDecimal> batch  = null;
	private int numRecs = 0;
	private IRSTransmissionService irsTransmissionService = null;
	private PersonService personService;
	private MECPeriodDAO mecPeriodDAO = null;
	private PersonMergeService personMergeService;
	private String BATCH_TYPE = "V";
	private String CORRECTION_IND = "O";
	private boolean onLineTraits = true;
	private PersonTraitsDAO personTraitsDAO = null;
	private LookupService lookupService = null;

	protected void executeProcess(DataProcessExecutionContext context) throws Exception {

		//supported custom args are calendar year and use local traits
		//calendar year to default to prevoius year if not identified, this should be typical
		//override calendar year only if need to start current year transmissions early or bulk retransmit an older year
		this.setThreadPoolSize(DEFAULT_THREAD_POOL_SIZE);
		onLineTraits = true;
		String args = (String)context.getExecutionArguments();
		if (!StringUtils.isEmpty(args)) {
			String[] params = args.split(",");
			if (params != null && params.length == 2) {
				onLineTraits = false;
				calendarYear = params[1].trim();
			} else if (params !=null && params.length == 1){
				onLineTraits = false;
			}
		}

		if (calendarYear == null) {
			Calendar prevYear = Calendar.getInstance();
		    prevYear.add(Calendar.YEAR, -1);
		    calendarYear = Integer.toString(prevYear.get(Calendar.YEAR));
		}
		String startDate = calendarYear + startDay;

		String size = this.getSystemParameterService().getByName(TRANSMISSION_SIZE).getValue();
		maxBatchSize = Integer.valueOf(size);
		setFetchSize(maxBatchSize);
		batch = new ArrayList<BigDecimal>();

		logger.debug("threads: " + getThreadPoolSize());
		logger.debug("year: " + calendarYear);

		this.setParamNames(new String[] {"startDate", "taxYear"});
		this.setParamValues(new String[] {startDate, calendarYear});
		this.setFetchSize(maxBatchSize);

		super.executeProcess(context);

	}
	@Override
	protected List doAcquireData(DataQueryProcessExecutionContext context) throws Exception {
		List acquiredData = null;
		acquiredData = executeQuery(context);

		return acquiredData;
	}

	@Override
    protected List executeQuery(DataQueryProcessExecutionContext context) throws Exception {
		DataQueryDetail currentQuery = context.getCurrentDataQuery();
		List results = getDao().find(currentQuery.getQuery().getQuery(), currentQuery.getQuery().getParamNames(),
        		currentQuery.getQuery().getParamValues(), numRecs, maxBatchSize, maxBatchSize);

		numRecs += maxBatchSize;

        return results;

    }

	protected void processData(DataQueryProcessExecutionContext context,
			List acquiredData) {

		if (acquiredData == null){
			return;
		}
		ArrayList<BigDecimal> submission = null;
		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));
			}
 		}
		submission = new ArrayList<BigDecimal>(batch);
		batch.clear();
		processEntityData(context, submission, calendarYear);
	}

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

		processBatch(context, submitBatch, calendarYear);

	}

	public void processBatch(DataQueryProcessExecutionContext context, ArrayList<BigDecimal> submitBatch, String calendarYear) {
		BigDecimal personId = null;
		ArrayList<IRS1095B> list = new ArrayList<IRS1095B>();
		IRS1095B vetRecord = null;
		MECPeriod period = null;
		try {
			for (int i = 0; i < submitBatch.size(); i++) {

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

				try {
					personId = (BigDecimal)submitBatch.get(i);

					if (this.getPersonMergeService().hasDeprecatedRecord(CommonEntityKeyFactory.createPersonIdEntityKey(personId))) {
						logger.error("Skipping Deprecated record found for person: " + personId);
					} else {
						period = new MECPeriod();
						period = this.getIrsTransmissionService().combineAllPeriods(personId, Integer.valueOf(calendarYear));

						if (period.isCoveredAll12Months() == false && period.getCoverageMonths().isEmpty()) {
							continue;
						}


						vetRecord = new IRS1095B();
						vetRecord = build1095(period);

						if (vetRecord == null) {
							logger.error("Failed to build 1095 from person: " + personId);
							context.getProcessStatistics().incrementNumberOfErrorRecords();
						} else {
							list.add(vetRecord);
							context.getProcessStatistics().incrementNumberOfSuccessfulRecords();
						}
					}

				} catch (Exception ex) {
					logger.error("Batch sumission failed to process record: " + personId + " " + ex.getMessage());
					context.getProcessStatistics().incrementNumberOfErrorRecords();
				}
			}
			if (list != null && list.size() > 0) {
				this.getIrsTransmissionService().sendSubmit(list, BATCH_TYPE, CORRECTION_IND, calendarYear);

			}
		} catch (Exception ex) {
			logger.error("Unknown Exception in GenerateIRSSubmission: " + ex.getMessage());
		}
	}


	private IRS1095B build1095(MECPeriod period) {
		IRS1095B rec = null;
		if (period != null) {
			try {
				Person person = null;
				if (onLineTraits) {
					person = personService.getPerson(new PersonIdEntityKeyImpl(period.getPersonId()));
				} else {
					PersonIdEntityKey personKey = CommonEntityKeyFactory.createPersonIdEntityKey(period.getPersonId());
					VPIDEntityKey vpid = this.getPersonService().getVPIDByPersonId(personKey);
					person = personService.getPersonWithoutIdentityTraits(vpid);

					PersonTraits pt = this.getPersonTraitsDAO().getPersonTraitsByVPID(person.getVPIDValue());

					if (pt == null) throw new Exception("Failed to Retrieve Person");

					PersonIdentityTraits traits = new PersonIdentityTraits();

					Name name = new Name();
		    		name.setFamilyName(pt.getFamilyName());
		    		name.setGivenName(pt.getGivenName());
		    		name.setMiddleName(pt.getMiddleName());
		    		name.setType(lookupService.getNameTypeByCode(NameType.LEGAL_NAME.getName()));
		    		traits.addName(name);

		    		SSN ssn = new SSN();
		    		ssn.setSsnText(pt.getSsn());
		    		ssn.setType(lookupService.getSSNTypeByCode(SSNType.CODE_ACTIVE.getName()));
		    		traits.setSsn(ssn);

		    		BirthRecord birthRecord = new BirthRecord();
		    		birthRecord.setBirthDate(new ImpreciseDate(pt.getBirthDate()));
		    		traits.setBirthRecord(birthRecord);

		    		traits.setGender(lookupService.getGenderByCode(pt.getGender()));

					person.setIdentityTraits(traits);
				}


				if (person == null) throw new ServiceException ("failed to retrieve person");

				rec = new IRS1095B(person);
				rec.setMecPeriod(period);

			} catch (ServiceException e) {
				logger.error("Generate IRS Process Failed to Retrieve person by id:" + period.getPersonId() + " , message:" + e.getMessage());
			}
			catch (Exception ex) {
				logger.error("GenerateIRSSubmitProcess Failed to build1095 for person: " + period.getPersonId() + " reason:" + ex.getMessage());
			}
		}
		return rec;
	}


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


	public SystemParameterService getSystemParameterService() {
		return systemParameterService;
	}

	public void setSystemParameterService(
			SystemParameterService systemParameterService) {
		this.systemParameterService = systemParameterService;
	}

	/**
	 * @return Returns the threadPoolSize.
	 */
	public int getThreadPoolSize() {
		return threadPoolSize;
	}

	public int getFetchSize() {
		return fetchSize;
	}

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


	public String getSpawnedTaskId() {
		return spawnedTaskId;
	}

	public void setSpawnedTaskId(String spawnedTaskId) {
		this.spawnedTaskId = spawnedTaskId;
	}


	/**
	 * @param threadPoolSize The threadPoolSize to set.
	 */
	public void setThreadPoolSize(int threadPoolSize) {
		this.threadPoolSize = threadPoolSize;
	}
	public IRSTransmissionService getIrsTransmissionService() {
		return irsTransmissionService;
	}
	public void setIrsTransmissionService(
			IRSTransmissionService irsTransmissionService) {
		this.irsTransmissionService = irsTransmissionService;
	}
	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 PersonTraitsDAO getPersonTraitsDAO() {
		return personTraitsDAO;
	}
	public void setPersonTraitsDAO(PersonTraitsDAO personTraitsDAO) {
		this.personTraitsDAO = personTraitsDAO;
	}
	public LookupService getLookupService() {
		return lookupService;
	}
	public void setLookupService(LookupService lookupService) {
		this.lookupService = lookupService;
	}

	 /*public void afterPropertiesSet() {
			super.afterPropertiesSet();
		}*/

}
