/**
 * 
 */

package gov.va.med.cds.integration;

import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.filter.EntryFilterInterface;
import gov.va.med.cds.filter.ParameterMapInterface;
import gov.va.med.cds.filter.QueryParameter;
import gov.va.med.cds.persistence.PersistenceException;
import gov.va.med.cds.persistence.QueryAssociationInterface;
import gov.va.med.cds.persistence.ReadException;
import gov.va.med.cds.persistence.hibernate.QueryParameterTransformerInterface;
import gov.va.med.cds.response.strategy.VistaGenericStoredProcedureResponseStrategyInterface;
import gov.va.med.cds.util.CipherUtilities;
import gov.va.med.cds.util.TimeoutUtil;

import java.lang.reflect.Method;
import java.net.SocketException;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import javax.sql.DataSource;

import org.hibernate.Session;
import org.springframework.orm.hibernate4.SessionFactoryUtils;

import com.intersys.jdbc.CacheConnection;

/**
 * @author DNS   egberb
 * 
 */
public class VistaGenericStoredProcedureQueryTimeoutQueryWork 
extends	VistaStoredProcedureQueryTimeoutQueryWork 
{

	private static final String STORED_PROCEDURE_NAME = "GenericObservationRead1.GSP";

	// The parameter DFN for the patient.
	private static final String PATIENT_ID = "patientId";

	// Must match one of the predefined list of parameters. We will let the VistA system  validate the value so that CDS is not tightly couple 
	//to the allowable parameters.
	private static final String DOMAIN = "domain";

	// Date range used by the M API to constrain the results. Must be in format ????????.
	private static final String START = "start";

	private static final String STOP = "stop";

	private static final String RECORD_ID = "id";

	private static final byte[] KEY = {104, 122, 80, 48, 57, 111, 76, 80, 50, 48, 79, 50, 75, 99, 66, 82};
	
	private static final String CACHE_DATE_FORMAT = "yyyyMMddHHmmss";
	
	private static final String ACCESS_LEVEL = "3X";

	private static final byte[] EKEY = {83, -88, 66, 69, 29, -48, 35, -9, 46, 117, -61, -13, 48, -85, 32, -65};
	
	private static String VISTA_FIXED_ENV_PARAM = "|E:DT=$P($$NOW^XLFDT,\".\",1)|E:DUZ(\"AG\")=\"\"|E:IO=0|E:DUZ=1|E:DUZ(2)=0|E:U=\"^\"|O:AUDIT=0|O:MRC=200000'"; // fixed environment value required by GSP.

	private String username; // injected through query strategy class.

	private String password; // encrypted and encoded and injected through query strategy class.

	private String rpcName; // name of RPC to be invoked including information.

	private VistaGenericStoredProcedureResponseStrategyInterface vistaGenericResponseStrategy;

	/**
	 * @param session
	 * @param queryAssociation
	 * @param entryFilter
	 * @param personIdentifiers
	 * @param parameterTransformerMap
	 * @param queryMap
	 * @param vistaResponseStrategy
	 * @param applicationName
	 * @param siteId
	 */
	public VistaGenericStoredProcedureQueryTimeoutQueryWork(Session session, QueryAssociationInterface queryAssociation, EntryFilterInterface entryFilter, List<String> personIdentifiers, Map<String, QueryParameterTransformerInterface> parameterTransformerMap,
												Map<String, String> queryMap, VistaGenericStoredProcedureResponseStrategyInterface vistaGenericResponseStrategy, String applicationName, String siteId, String userName, String password, String rpcName) 
	{
		super(session, queryAssociation, entryFilter, personIdentifiers, parameterTransformerMap, queryMap, null, applicationName, siteId);

		setUsername(userName);
		setPassword(password);
		setRpcName(rpcName);
		setVistaGenericResponseStrategy(vistaGenericResponseStrategy);

		if (System.getProperty("fpdsVistAEnvParam") != null) 
		{
			VISTA_FIXED_ENV_PARAM = System.getProperty("fpdsVistAEnvParam");
		}

	}

	@Override
	protected String buildQueryString(String associationName, EntryFilterInterface aEntryFilter, List<String> personIdentifiers)
	throws ReadException 
    {

		return this.queryMap.get(STORED_PROCEDURE_NAME);
	}

	@Override
	protected List<String> buildQueryParameterList(String query, String associationName, EntryFilterInterface aEntryFilter, List<String> personIdentifiers) 
	{

		List<String> spParameters = new ArrayList<String>();

		// Add the user to the list of params
		spParameters.add(this.username);

		// Decrypt the password and add timestamp and access level and encrpt it. Add the encrypted / base64 encoded password to the list of params
		try 
		{
	
			SimpleDateFormat sdf = new SimpleDateFormat(CACHE_DATE_FORMAT);
			sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
			String datePlain = sdf.format(new Date());
			
			String decryptedPasswd = CipherUtilities.decrypt(EKEY, password);
			String newEncryptPassword = CipherUtilities.encrypt(ACCESS_LEVEL + datePlain + decryptedPasswd, KEY);
			
			spParameters.add(newEncryptPassword);
			
			
		} catch (Exception e) 
		{
			throw new PersistenceException(ErrorCodeEnum.ROOT_CAUSE_MSG, e, "Error creating encrypted timestamp required by GSP.");
		}

		// Add the RPC naem to the parameters list.
		if (this.rpcName == null | "".equals(this.rpcName)) 
		{
			throw new PersistenceException(ErrorCodeEnum.INVALID_OR_UNEXPECTED_QUERY_PARAM, "RPC NAME");
		}
		spParameters.add(this.rpcName);

		// Map used to generate the M API parameter string.
		Map<String, String> mApiParams = new HashMap<String, String>();

		// extract patient identifier parameter from filter XML
		mApiParams.put(PATIENT_ID, extractVistaPatientIdentifier(personIdentifiers));

		// extract start and end date filter parameters from filter XML. Only include them if there is a non-null date value.
		String dateRangeStart = extractVistaDateFromFilter(aEntryFilter.getStartDate(), DEFAULT_START_DATE);
		String dateRangeEnd = extractVistaDateFromFilter(aEntryFilter.getEndDate(), DEFAULT_END_DATE);

		if (dateRangeEnd != null && !"".equals(dateRangeEnd)) 
		{
			mApiParams.put(STOP, dateRangeEnd);
		}

		if (dateRangeStart != null && !"".equals(dateRangeStart)) 
		{
			mApiParams.put(START, dateRangeStart);
		}

		// extract domain from the domainEntryPoint filter element 
		if (aEntryFilter.getDomainEntryPoint() == null || "".equals(aEntryFilter.getDomainEntryPoint())) 
		{
			throw new PersistenceException(ErrorCodeEnum.INVALID_OR_UNEXPECTED_QUERY_PARAM, DOMAIN);
		}
		
		mApiParams.put(DOMAIN, aEntryFilter.getDomainEntryPoint());

		// extract the record identifier element from the filter XML
		if (aEntryFilter.getRecordIdentifiers() != null) 
		{
			if (aEntryFilter.getRecordIdentifiers().size() > 1) 
			{
				throw new PersistenceException(ErrorCodeEnum.INVALID_OR_UNEXPECTED_QUERY_PARAM, RECORD_ID);
			} 
			else if (aEntryFilter.getRecordIdentifiers().size() == 1)
			{
				mApiParams.put(RECORD_ID, aEntryFilter.getRecordIdentifiers().get(0));
			}
		}

		// extract the optional parameters from the filter XML
		ParameterMapInterface parameterMap = aEntryFilter.getAdditionalParametersMap();
		for (String filterParameterName : parameterMap.getFilterParameterNames()) 
		{
			QueryParameter<?> queryParameter = parameterMap.getParameterValue(filterParameterName);
			if (queryParameter == null) 
			{
				throw new PersistenceException(ErrorCodeEnum.INVALID_OR_UNEXPECTED_QUERY_PARAM, filterParameterName);
			}

			if ("dateRange".equals(queryParameter.getType())) 
			{
				throw new PersistenceException(ErrorCodeEnum.INVALID_OR_UNEXPECTED_QUERY_PARAM,	filterParameterName);
			} 
			else 
			{
				if (!(queryParameter.getValue() instanceof String)) 
				{
					throw new PersistenceException(ErrorCodeEnum.INVALID_OR_UNEXPECTED_QUERY_PARAM, filterParameterName);
				}
				else 
				{ 
					mApiParams.put(queryParameter.getName(), (String)
					queryParameter.getValue()); 
				}
			}
		}

		// build the parameter list from the filter		
		spParameters.add(buildGenericStoredProcdureParameterString(mApiParams));

		return spParameters;

	}

	private String buildGenericStoredProcdureParameterString(Map<String, String> mApiParams) 
	{

		StringBuffer sbParams = new StringBuffer();

		Iterator<String> iter = mApiParams.keySet().iterator();
		while (iter.hasNext()) 
		{
			String paramName = iter.next();
			sbParams.append(String.format("A:FILTER(\"%s\")=\"%s\"", paramName, mApiParams.get(paramName)));
			
			if (iter.hasNext()) 
			{
				sbParams.append("|");
			}
		}

		sbParams.insert(0, "A:FILTER(\"max\")=100000|");

		sbParams.append(VISTA_FIXED_ENV_PARAM);

		return sbParams.toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() 
	{
		CallableStatement cstmt = null;
		Connection pooledConnection = null;
		Connection wrappedConnection = null;
		ResultSet rs = null;

		try 
		{
			String query = buildQueryString(queryAssociation.getAssociationName(), entryFilter,	personIdentifiers);
			
			List<Map<String, Object>> resultList = new ArrayList<Map<String, Object>>();

			DataSource dataSource = SessionFactoryUtils.getDataSource(session.getSessionFactory());

			wrappedConnection = dataSource.getConnection();

			pooledConnection = pooledConnectionUnwrapper.unwrap(wrappedConnection);

			cstmt = pooledConnection.prepareCall(query);
			cstmt.setInt(1, 800);

			cstmt.setQueryTimeout(1);//Ignoring spring context query timeout setting to fail fast.            
			rs = cstmt.executeQuery();
			List<String> records = new ArrayList<String>();
			while (rs.next()) 
			{
				String record = rs.getString(1);
				records.add(record);
			}

			if ((records != null) && (records.size() > 0)) 
			{
				this.results = vistaGenericResponseStrategy.formatResponse(records,	extractVistaPatientIdentifier(personIdentifiers), siteId);
			}
		} catch (SocketException se) 
		{
			addException(se, applicationName);
		} catch (Exception ex)
		{
			addException(ex, applicationName);
		} finally 
		{
			try 
			{
				if (rs != null) {
					rs.close();
					rs = null;
				}

				if (cstmt != null) {
					cstmt.close();
					cstmt = null;
				}

				if (wrappedConnection != null) {
					wrappedConnection.close();
					wrappedConnection = null;
				}
			} catch (Exception ex) {
				cstmt = null;
				pooledConnection = null;
			}
		}
	}

	public void setUsername(String username) 
	{
		this.username = username;
	}

	public void setPassword(String password) 
	{
		this.password = password;
	}

	public void setRpcName(String rpcName) 
	{
		this.rpcName = rpcName;
	}

	public void setVistaGenericResponseStrategy(VistaGenericStoredProcedureResponseStrategyInterface vistaGenericResponseStrategy) 
	{
		this.vistaGenericResponseStrategy = vistaGenericResponseStrategy;
	}

	
}
