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

import static gov.va.med.cds.exception.ErrorCodeEnum.HDRII_OPERATION_FAILED;
import gov.va.med.cds.clinicaldata.DomainEntryPoint;
import gov.va.med.cds.clinicaldata.Operation;
import gov.va.med.cds.exception.ErrorCodeEnum;
import gov.va.med.cds.exception.OperationNotSupportedException;
import gov.va.med.cds.persistence.PersistenceException;
import gov.va.med.cds.persistence.ReadException;
import gov.va.med.cds.persistence.WritePersistenceManagerInterface;
import gov.va.med.cds.rules.BooleanRuleInterface;
import gov.va.med.cds.template.generated.common.PatientLite;
import gov.va.med.cds.template.generated.surveyscommon.CareCoordinator;
import gov.va.med.cds.template.generated.surveyscommon.Facility;
import gov.va.med.cds.template.generated.surveyscommon.Survey;
import gov.va.med.cds.template.generated.surveyscommon.SurveyChoice;
import gov.va.med.cds.template.generated.surveyscommon.SurveyPatient;
import gov.va.med.cds.template.generated.surveyscommon.SurveyQuestion;
import gov.va.med.cds.template.generated.surveyscommon.SurveyQuestionAndAnswer;
import gov.va.med.cds.template.generated.surveyscommon.SurveyQuestionsAndAnswers;
import gov.va.med.cds.template.generated.surveyscommon.SurveyResponse;
import gov.va.med.cds.template.generated.surveyscommon.Vendor;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.dom4j.Element;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Example;
import org.hibernate.exception.ConstraintViolationException;

public class WriteableSurveyPersistenceManager extends
		AbstractHibernatePersistenceManager implements
		WritePersistenceManagerInterface {

	private static final String SURVEY_QS_AND_AS = "surveyQuestionsAndAnswers";
	private static final String SURVEY_TYPE = "type";
	private static final String SURVEY_PATIENT = "surveyPatient";
	private static final String PATIENT = "patient";
	private static final String CARE_COORDINATOR = "careCoordinator";
		
	private List<BooleanRuleInterface> rules;
	private String cdsAppName;
	
	

	public boolean isApplicable(Object critera) throws PersistenceException {
		boolean b = false;
		for (BooleanRuleInterface r : rules) {
			b = r.evaluate(critera);
			if (b) {
				break;
			}
		}

		return b;
	}

	public String performCUADOnClinicalData(String templateId,
			Operation operation, Object aObject, DomainEntryPoint domainEntryPoint)
			throws PersistenceException {
		Session session = null;
		String resultId = "";

		// we know this is a survey so we need to find values from lookup table
		// and use existing records.

		try {
			session = getSessionFactory(templateId).getCurrentSession();

			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug(gov.va.med.cds.util.LogMessageUtil.buildMessage(
						templateId, null, cdsAppName, operation.name()
								+ aObject));
			}

			switch (operation) {
			case Create:
				resultId = createData(session, aObject, domainEntryPoint);
				break;

			default:
				throw new UnsupportedOperationException(
						String.format(
								"The %s operation is not supported by the WriteableHibernatePersistenceManager.",
								operation.toString()));
			}

			session.flush();
			session.clear();
			return resultId;
		}
		// try to figure out what caused the violation
		catch (ConstraintViolationException e) {
			throw new HibernateConstraintViolationException(e);
		}
		// already handled and turned into one of ours so rethrow it
		catch (PersistenceException e) {
			throw e;
		}
		// any other problems are just big and will require someone to look at
		// so check log file
		catch (Exception e) {
			String rootCauseMessage = ExceptionUtils.getRootCause(e) == null ? e
					.getMessage() : ExceptionUtils.getRootCause(e).getMessage();
			throw new PersistenceException(HDRII_OPERATION_FAILED, e,
					operation.name(), rootCauseMessage, domainEntryPoint.getName());
		}
	}
	
	

	

	/**
	 * Handles hibernate HDRII create processing.
	 * 
	 * @param session
	 *            - active hibernate session.
	 * @param aObject
	 *            - create request.
	 */
	protected String createData(Session session, Object aObject, DomainEntryPoint domainEntryPoint) {
		
		//Element surveyResponse = (Element) aObject;
		SurveyResponse surveyResponse  = (SurveyResponse)unmarshalElementToObject((Element)aObject, domainEntryPoint);
		
		validateAndRetrieveVendor(session, surveyResponse);
		validateAndRetrieveFacility(session, surveyResponse);
		retrieveOrStoreSurveyPatient(session, surveyResponse);
		retrieveOrStoreSurveyCareCoordinator(session, surveyResponse);
		storeSurveyAnswers(session, surveyResponse);
		return session.save(surveyResponse).toString();
	}

	/**
	 * RQ 279.8 HDR shall only persist HTH survey data to the HDR DB when the
	 * survey status is �ACCEPTED� and the data contains an answer for every
	 * question for the corresponding survey version.
	 * 
	 * @param session
	 * @param aSurveyResponse
	 */
	@SuppressWarnings("unchecked")
	private void storeSurveyAnswers(Session session, SurveyResponse aSurveyResponse) {
		List<Survey> results = null;
		Survey incomingSurvey = aSurveyResponse.getSurvey();
		Criteria criteria;

		try {
			criteria = session.createCriteria("survey");
			criteria.add(Example.create(incomingSurvey));
			results = criteria.list();

			if (results.size() != 1) {
				throw new IdentityException(
						ErrorCodeEnum.WRITE_REQUEST_SURVEY_SURVEYID_INVALID,
						incomingSurvey.getSurveyTitle());
			}

			Survey survey = results.get(0);
			// for saving survey id in response
			aSurveyResponse.setSurvey(survey);
			
			SurveyQuestionsAndAnswers surveyQuestionsAndAnswers = aSurveyResponse.getSurveyQuestionsAndAnswers(); 
						
			if (aSurveyResponse.getResponseStatus().equalsIgnoreCase("ACCEPTED")) {
				
				//TODO check with Jalpan on original logic - not clear about 'matching algorith etc...
				
				// Because this is different depending upon the type of  
				// survey submitted, we have to use a matching algorithm
				// to get the name of the element.
				List<SurveyQuestion> surveyQuestions = survey.getSurveyQuestions();
				List<SurveyQuestionAndAnswer> responseQAs = surveyQuestionsAndAnswers.getSurveyQuestionAndAnswer();
				
				// Schema will validate that there are no duplicate answers for
				// the same question.
				// DB will ensure the question/answer combination exists,
				// so check that there are the correct number of answers for
				// this survey.
				
				//TODO figure out why the list is getting null for first element in list increase the size by 1
				surveyQuestions.removeAll(Collections.singleton(null));
				responseQAs.removeAll(Collections.singleton(null));
				
				int numQuestions = (surveyQuestions == null) ? -1 : surveyQuestions.size();
				int numSurveyResponses = (responseQAs == null) ? -1 : responseQAs.size();
				
				if (numQuestions == -1 || numSurveyResponses == -1 || numQuestions != numSurveyResponses) {
					
					throw new PersistenceException(
							ErrorCodeEnum.WRITE_REQUEST_SURVEY_RESPONSES_INVALID,
							Integer.toString(numQuestions), Integer
									.toString(numSurveyResponses));
				}

				for(SurveyQuestionAndAnswer answer:responseQAs){
					
					Integer answerQuestionNumber = answer.getQuestionNumber();
					Integer answerQuestionAnswer = answer.getAnswer();

					// get question
					SurveyQuestion surveyQuestion = getQuestion(answerQuestionNumber,
							surveyQuestions);
					SurveyChoice surveyChoice = getChoice(answerQuestionAnswer,
							surveyQuestion);
					
					answer.setSurveyChoice(surveyChoice);
					answer.setQuestionNumber(null);
					answer.setAnswer(null);
					
				}
			} else { // only store the metadata, so don't store answers
				aSurveyResponse.setSurveyQuestionsAndAnswers(null);
			}
		} catch (org.hibernate.exception.GenericJDBCException e) {
			throw new ReadException(ErrorCodeEnum.HIBERNATE_READ_FAILURE, e,
					e.getMessage());
		}
	}
	

	private String getElementNameEndingWith(Element parent, String endsWith) {
		
		@SuppressWarnings("rawtypes")
		Iterator childIterator = parent.elementIterator();
		while(childIterator.hasNext())
		{
			Element child = (Element)childIterator.next();
			if(child.getName().endsWith(endsWith))
			{
				return child.getName();
			}
		}
		
		throw new IllegalArgumentException("Survey response contains must contain questions and answers.");
	}

	private SurveyQuestion getQuestion(Integer questionNumber,
			List<SurveyQuestion> questionList) {
		// /surveyQuestion/question/questionNumber[text()="2"]
		for (SurveyQuestion question : questionList) {
			if (question.getQuestionNumber().equals(questionNumber)) {
				return question;
			}
		}
		return null;
	}

	private SurveyChoice getChoice(Integer answerNumber, SurveyQuestion question) {
		@SuppressWarnings("unchecked")
		List<SurveyChoice> choices = question.getSurveyChoices();
		choices.removeAll(Collections.singleton(null));
		for (SurveyChoice choice : choices) {
			if (choice.getChoiceNumber().equals(answerNumber)) {
				return choice;
			}
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	protected void retrieveOrStoreSurveyPatient(Session session, SurveyResponse response) {
		List<SurveyPatient> results = null;
		SurveyPatient surveyPatient = response.getSurveyPatient();

		Criteria criteria = session.createCriteria(SURVEY_PATIENT);
		criteria.add(Example.create(surveyPatient));
		try {
			results = criteria.list();
		} catch (org.hibernate.exception.GenericJDBCException e) {
			throw new ReadException(ErrorCodeEnum.HIBERNATE_READ_FAILURE, e,
					e.getMessage());
		}

		if (results.size() < 1) {
			session.save(SURVEY_PATIENT, surveyPatient);
		} 
		else {
			
				PatientLite patient = surveyPatient.getPatient();
				SurveyPatient firstSurveyPatient = results.get(0);
				firstSurveyPatient.setPatient(patient);
			
				response.setSurveyPatient(firstSurveyPatient);
		}
	}
	
	@SuppressWarnings("unchecked")
	protected void retrieveOrStoreSurveyCareCoordinator(Session session, SurveyResponse response) {
		List<CareCoordinator> results = null;
		CareCoordinator careCoordinator = response.getCareCoordinator();

		Criteria criteria = session.createCriteria(CARE_COORDINATOR);
		criteria.add(Example.create(careCoordinator));
		try {
			results = criteria.list();
		} catch (org.hibernate.exception.GenericJDBCException e) {
			throw new ReadException(ErrorCodeEnum.HIBERNATE_READ_FAILURE, e,
					e.getMessage());
		}

		if (results.size() < 1) {
			session.save(CARE_COORDINATOR, careCoordinator);
		}
		else{
			response.setCareCoordinator(results.get(0));
		}
	}
	
	

	/**
	 * HTH now only wants us to validate the vendor number, not the name. So
	 * make sure the vendor number exists, and if there is one or more, look for
	 * the one with the same name. If there isn't a match on name, create a new
	 * vendor entry.
	 * 
	 * @param session
	 *            hibernate session
	 * @param aSurvey
	 *            the survey to store
	 */
	@SuppressWarnings("unchecked")
	protected void validateAndRetrieveVendor(Session session,
			SurveyResponse aSurveyResponse) {
		List<Vendor> results = null;
		boolean foundVendor = false;

		Vendor incomingVendor = aSurveyResponse.getVendor();

		String vendorName = incomingVendor.getVendorName();
		String vendorNameToStore = vendorName;
		
		//lookup vedors by vendorNumber only - remove vendorName for lookup query
		incomingVendor.setVendorName(null);
		Criteria criteria = session.createCriteria("vendor");
		criteria.add(Example.create( incomingVendor));
		try {
			results = criteria.list();
		} catch (org.hibernate.exception.GenericJDBCException e) {
			throw new ReadException(ErrorCodeEnum.HIBERNATE_READ_FAILURE, e,
					e.getMessage());
		}

		if (results.size() < 1) {
			// vendor number doesn't exist
			throw new ReadException(
					ErrorCodeEnum.WRITE_REQUEST_SURVEY_VENDOR_NUMBER_INVALID,
					incomingVendor.getVendorNumber());
		}

		for (Vendor vendor : results) {
			if (vendor.getVendorName().equals(vendorNameToStore)) {
				aSurveyResponse.setVendor(vendor);
				foundVendor = true;
				break;
			}
		}

		if (!foundVendor) {
			incomingVendor.setVendorName(vendorName);
			session.save("vendor", incomingVendor);
		}
	}

	@SuppressWarnings("unchecked")
	protected void validateAndRetrieveFacility(Session session,
			SurveyResponse aSurveyResponse) {
		List<Facility> results = null;
		Facility incomingFacility = aSurveyResponse.getFacility();

		Criteria criteria = session.createCriteria("facility");
		criteria.add(Example.create(incomingFacility));
		try {
			results = criteria.list();
		} catch (org.hibernate.exception.GenericJDBCException e) {
			throw new ReadException(ErrorCodeEnum.HIBERNATE_READ_FAILURE, e,
					e.getMessage());
		}

		if (results.size() != 1) {
			throw new ReadException(
					ErrorCodeEnum.WRITE_REQUEST_SURVEY_FACILITY_INVALID,
					incomingFacility.getFacilityNumber());
		}

		aSurveyResponse.setFacility(results.get(0));

	}

	/**
	 * Sets the rules to evaluate to determine if the persistence manager is
	 * applicable for the request.
	 * 
	 * @param rules
	 *            The boolean rules to be evaluated.
	 */
	public void setRules(List<BooleanRuleInterface> rules) {
		this.rules = rules;
	}

	public void setCdsAppName(String cdsAppName) {
		this.cdsAppName = cdsAppName;
	}
	

   @Override
    public String performCUADOnPatientsData( String templateId, String aRequestId, Operation aOperation, Object aObject, DomainEntryPoint domainEntryPoint )
        throws PersistenceException {
		throw new OperationNotSupportedException(ErrorCodeEnum.METHOD_NOT_IMPLEMENTED);
    	//return "";
    }
	
/*
	@Override
	public List<CensusMasterIngest> performReadOnCensusMetaData(
			CensusMasterIngest censusMasterIngest) throws CensusException {
		throw new OperationNotSupportedException(ErrorCodeEnum.METHOD_NOT_IMPLEMENTED);
	}

	@Override
	public String performCUADOnCensusMetaData(Operation aOperation,
			CensusMasterIngest censusMasterIngest) throws PersistenceException {
		throw new OperationNotSupportedException(ErrorCodeEnum.METHOD_NOT_IMPLEMENTED);
	}
*/
	
}
