
package gov.va.med.cds.persistence.hibernate;

import java.net.SocketException;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.sql.DataSource;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.springframework.jdbc.support.lob.DefaultLobHandler;
import org.springframework.orm.hibernate4.SessionFactoryUtils;
import org.springframework.util.StringUtils;

import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.exceptionframework.ExceptionHandler;
import gov.va.med.cds.filter.EntryFilterInterface;
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.response.strategy.VistaStoredProcedureResponseStrategyInterface;
import gov.va.med.cds.util.TimeoutUtil;

public class VistaStoredProcedureWriteWork extends VistaStoredProcedureQueryWork {

	private Map<String, Set<String>> templateIdToDataListMap;

	public VistaStoredProcedureWriteWork(Session session, QueryAssociationInterface queryAssociation,
			EntryFilterInterface entryFilter, List<String> personIdentifiers,
			Map<String, QueryParameterTransformerInterface> parameterTransformerMap, Map<String, String> queryMap,
			VistaStoredProcedureResponseStrategyInterface vistaResponseStrategy, String applicationName,
			String siteId) {
		super(session, queryAssociation, entryFilter, personIdentifiers, parameterTransformerMap, queryMap, null,
				applicationName, siteId);

	}

	private String buildQueryString(String associationName, EntryFilterInterface aEntryFilter) throws ReadException {
		String sqlQuery = null;
		String queryName = String.format("%s.%s.%s", entryFilter.getTemplateId(), entryFilter.getDomainEntryPoint(),
				associationName);

		sqlQuery = queryMap.get(queryName);

		return sqlQuery;
	}

	private String extractVistaPatientIdentifier(String pid) {
		QueryParameterTransformerInterface pidsParameterTransformer = this.parameterTransformerMap.get("pids");
		if (pidsParameterTransformer == null) {
			throw new ReadException(ErrorCodeEnum.PARAMETER_TRANSFORMER_EXPECTED_ERROR, "pids");
		}
		String patientIdentifier = pidsParameterTransformer.transform(pid).toString();

		return patientIdentifier;
	}

	@SuppressWarnings("rawtypes")
	protected List<String> buildQueryParameterList(String query, String associationName,
			EntryFilterInterface aEntryFilter) {
		ArrayList<String> namedParameters = new ArrayList<String>();

		namedParameters.add(aEntryFilter.getRequestId());
		namedParameters.add("call name");
		namedParameters.add(extractVistaPatientIdentifier(personIdentifiers.get(0)));

		Set<String> paramNames = this.templateIdToDataListMap.get(aEntryFilter.getTemplateId());
		Map<String, Object> parameterMap = aEntryFilter.getDataMap();
		if (parameterMap != null) {
			for (String paramName : paramNames) {
				namedParameters.add((String) parameterMap.get(paramName));
			}
		} else {
			namedParameters.add("");
		}
		return namedParameters;
	}

	/*
	 * (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);
			List<String> namedParameters = buildQueryParameterList(query, queryAssociation.getAssociationName(),
					entryFilter);
			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);

			for (int i = 0; i < namedParameters.size(); i++) {
				cstmt.setString(i + 1, namedParameters.get(i));
			}

			long queryTimeout = TimeoutUtil.processTimeout(getTemplateTimeoutMap(), entryFilter, this.applicationName);
			cstmt.setQueryTimeout((int) queryTimeout);

			/// TODO wrap performance log around this call - be careful to wrap
			/// the finish time in the correct logical place...(end of jdbc blob
			/// streaming)
			rs = cstmt.executeQuery();

			while (rs.next()) {
				
				String returnError = rs.getString("returnError");
				if (StringUtils.hasLength(returnError)) 
				{
					addException(new Exception(returnError), applicationName);
				}
				
				DefaultLobHandler lobHandler = new DefaultLobHandler();
				Map<String, Object> results = new HashMap<String, Object>();
				byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "stream");
				results.put("stream", blobBytes);
				results.put("siteID", rs.getString("siteID"));
				results.put("params", rs.getString("params"));
				resultList.add(results);
			}

			if ((resultList != null) && (resultList.size() > 0) && (resultList.get(0).get("stream") != null)) {
				this.results = vistaResponseStrategy.formatResponse(resultList, entryFilter.getDomainEntryPoint(),
						entryFilter.getTemplateId());
			}
		} catch (SocketException se) {
			addException(se, applicationName);
		} catch (Exception ex) {
			addException(ex, applicationName);
		} finally {
			if (rs != null) {
				try {
					rs.close();
				} catch (Exception er) {
					er.printStackTrace();
				} finally {
					rs = null;
				}
			}
			if (cstmt != null) {
				try {
					cstmt.close();
				} catch (SQLException ec) {
					ec.printStackTrace();
				} finally {
					cstmt = null;
				}
			}
			if (pooledConnection != null) {
				try {
					pooledConnection.close();
				} catch (SQLException ep) {
					ep.printStackTrace();
				} finally {
					pooledConnection = null;
				}
			}
			if (wrappedConnection != null) {
				try {
					wrappedConnection.close();
				} catch (SQLException ew) {
					ew.printStackTrace();
				} finally {
					wrappedConnection = null;
				}
			}
		}
	}

	public void setVistaResponseStrategy(VistaStoredProcedureResponseStrategyInterface vistaResponseStrategy) {
		this.vistaResponseStrategy = vistaResponseStrategy;
	}

	public void setQueryMap(Map<String, String> queryMap) {
		this.queryMap = queryMap;
	}

	@Override
	public boolean isDaemon() {
		return false;
	}

	@Override
	public void release() {
		try {
			if (this.session != null && this.session.isOpen()) {
				this.session.cancelQuery();
				this.session.close();
			}
		} catch (HibernateException e) {
			addException(e, applicationName);
		} finally {
			session = null;
		}

	}

	@Override
	public QueryAssociationInterface getQueryAssociation() {
		return this.queryAssociation;
	}

	@Override
	public Object getResults() {
		return this.results;
	}

	@Override
	public List<Exception> getExceptions() {
		return this.exceptions;
	}

	@Override
	public void addException(Exception ex, String applicationName) {
		ExceptionHandler.logRootException(ex, entryFilter.getTemplateId(), entryFilter.getRequestId(), applicationName);
		this.exceptions.add(ex);
	}

	public void setTemplateIdToDataListMap(Map<String, Set<String>> templateIdToDataListMap) {
		this.templateIdToDataListMap = templateIdToDataListMap;
	}

}
