package com.agilex.healthcare.schedulingmanager.datalayer.appointment;

import com.agilex.healthcare.mobilehealthplatform.domain.filter.datefilter.DateFilter;
import com.agilex.healthcare.schedulingmanager.datalayer.AbstractDao;
import com.agilex.healthcare.schedulingmanager.datalayer.ValidPo;
import com.agilex.healthcare.schedulingmanager.datalayer.userhistory.UserHistoryEntityManager;
import com.agilex.healthcare.schedulingmanager.domain.*;
import com.agilex.healthcare.schedulingmanager.enumeration.AppointmentRequestStatus;
import com.agilex.healthcare.utility.NullChecker;

import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Query;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;

import java.util.*;

@Repository
public class AppointmentRequestDao extends AbstractDao {

    private static final org.apache.commons.logging.Log LOGGER = org.apache.commons.logging.LogFactory.getLog(AppointmentRequestDao.class);

	public SMAppointmentRequests getActiveAppointmentRequests(String patientId, DateFilter filter) {
		Query constructedQuery = this.constructWhereClause(patientId, filter);
		List<AppointmentRequestPo> retrievedRequests = this.executeForAppointmentRequests(constructedQuery);
		
		SMAppointmentRequests SMAppointmentRequests = new SMAppointmentRequests();
		for (AppointmentRequestPo retrievedRequest : retrievedRequests) {
			SMAppointmentRequests.add(retrievedRequest.create());
		}
		
		return SMAppointmentRequests;
	}

	public SMAppointmentRequests getAppointmentRequests(AppointmentRequestFilter appointmentRequestFilter) {
		Query constructedQuery = this.constructWhereClause(appointmentRequestFilter);
		List<AppointmentRequestPo> retrievedRequests = this.executeForAppointmentRequests(constructedQuery);
		
		SMAppointmentRequests smAppointmentRequests = new SMAppointmentRequests();
		for (AppointmentRequestPo retrievedRequest : retrievedRequests) {
			smAppointmentRequests.add(retrievedRequest.create());
		}
		
		return smAppointmentRequests;
	}

    public SMAppointmentRequests getAppointmentRequests(AppointmentRequestFilter appointmentRequestFilter, boolean freshStart, HashMap<String, Boolean> sortFields, String filterString, int  firstRecord, int  lastRecord) {
        Query constructedQuery = this.constructWhereClause(appointmentRequestFilter, sortFields, filterString);
        List<AppointmentRequestPo> retrievedRequests = this.executeForAppointmentRequests(constructedQuery);

        List<AppointmentRequestPo> filteredSMAppointmentRequests = new ArrayList<AppointmentRequestPo>();
        for (AppointmentRequestPo appointmentRequestPo : retrievedRequests) {
            if (isCancelledAppointmentRequest(appointmentRequestPo)) {
                if (NullChecker.isNotNullish(appointmentRequestPo.getAppointmentRequestDetailCode())) {
                    filteredSMAppointmentRequests.add(appointmentRequestPo);
                }
            } else {
                filteredSMAppointmentRequests.add(appointmentRequestPo);
            }
        }

        SMAppointmentRequests smAppointmentRequests = new SMAppointmentRequests();
        smAppointmentRequests.setRecordCount(filteredSMAppointmentRequests.size());

        int endRecord = (lastRecord <= filteredSMAppointmentRequests.size()) ? lastRecord : filteredSMAppointmentRequests.size();
        for(int i = firstRecord; i<endRecord; i++){
            smAppointmentRequests.add(filteredSMAppointmentRequests.get(i).create());
        }

        return smAppointmentRequests;
    }

	public SMAppointmentRequest getActiveAppointmentRequest(String patientId, String appointmentRequestId) {
		String selectSingleAppointmentRequest = "from AppointmentRequestPo where patient.userId = :patientId and id = :appointmentRequestId and activeFlag = true ";
		Query query = getQuery(selectSingleAppointmentRequest);
		query.setParameter("appointmentRequestId", appointmentRequestId);
		query.setParameter("patientId", patientId);

		AppointmentRequestPo po = this.executeForAppointmentRequest(query);
		SMAppointmentRequest request = (po == null)? null : po.create();
		
		return request;
	}

	public SMAppointmentRequest getAppointmentRequest(String patientId, String appointmentRequestId) {
		String selectSingleAppointmentRequest = "from AppointmentRequestPo where patient.userId = :patientId and id = :appointmentRequestId ";
		Query query = getQuery(selectSingleAppointmentRequest);
		query.setParameter("appointmentRequestId", appointmentRequestId);
		query.setParameter("patientId", patientId);


		AppointmentRequestPo po = this.executeForAppointmentRequest(query);
		SMAppointmentRequest request = (po == null)? null : po.create();
		
		return request;
	}
	
	public Set<SMDetailCode> getDetailCodes() {
		String selectDetailCodeQuery = "from DetailCodePo order by code";
		Query query = getQuery(selectDetailCodeQuery);
		@SuppressWarnings("unchecked")
		List<DetailCodePo> retrievedDetailCodes = query.getResultList();
		Set<SMDetailCode> codes = new HashSet<SMDetailCode>();
		
		for (DetailCodePo retrievedDetailCode : retrievedDetailCodes) {
			codes.add(retrievedDetailCode.create());
		}
		
		return codes;
	}

	public SMDetailCode getDetailCodeByCode(String code) {
		String selectDetailCodeQuery = "from DetailCodePo where code = :code";
		Query query = getQuery(selectDetailCodeQuery);
		query.setParameter("code", code);

		DetailCodePo detailCode = executeForDetailCode(query);
		return detailCode.create();
	}

	public SMAppointmentRequestMessages getAppointmentRequestMessagesByAppointmentRequestId(String patientId, String appointmentRequestId) {
		SMAppointmentRequestMessages messages = new SMAppointmentRequestMessages();
		
		SMAppointmentRequest parentRequest = null;
		
		try {
			parentRequest = this.getAppointmentRequest(patientId, appointmentRequestId);
		} catch (WebApplicationException e) {
			if (e.getResponse().getStatus() != Status.NOT_FOUND.getStatusCode()) {
				throw e;
			}
		}
			
		if (parentRequest != null) {
			String selectDetailCodeQuery = "from AppointmentRequestMessagePo where appointmentRequestId = :appointmentRequestId";
			Query query = getQuery(selectDetailCodeQuery);
			query.setParameter("appointmentRequestId", appointmentRequestId);
			List<AppointmentRequestMessagePo> retrievedMessages = executeForAppointmentRequestMessages(query);
			
			for (AppointmentRequestMessagePo retrievedMessage : retrievedMessages) {
				messages.add(retrievedMessage.create());
			}
		}
		
		return messages;
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public SMAppointmentRequestMessage saveAppointmentRequestMessage(String patientId, SMAppointmentRequestMessage smAppointmentRequestMessage, boolean isProvider) {
		// Second attempts will save two instances of the appointment request -
		// parent request and the second request.
		SMAppointmentRequest smAppointmentRequest = this.getAppointmentRequest(patientId, smAppointmentRequestMessage.getAppointmentRequestId());
		if(isProvider){
			smAppointmentRequest.setHasVeteranNewMessage(true);
		}
		else{
			smAppointmentRequest.setHasProviderNewMessage(true);
		}
		
        UserHistoryEntityManager<AppointmentRequestMessagePo> arMessageEntityManager = new UserHistoryEntityManager<AppointmentRequestMessagePo>(entityManager, AppointmentRequestMessagePo.TABLE_NAME, smAppointmentRequestMessage);
        AppointmentRequestMessagePo savedAppointmentRequestMessage = arMessageEntityManager.save(new AppointmentRequestMessagePo(smAppointmentRequestMessage));

        UserHistoryEntityManager<AppointmentRequestPo> simpleEntityManager = new UserHistoryEntityManager<AppointmentRequestPo>(entityManager, AppointmentRequestPo.TABLE_NAME, smAppointmentRequest);
        simpleEntityManager.save(new AppointmentRequestPo(smAppointmentRequest));
		
		return savedAppointmentRequestMessage.create();
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public SMAppointmentRequest updateAppointmentRequestMessageFlag(String patientId, String appointmentRequestId, boolean isProvider){
		SMAppointmentRequest SMAppointmentRequest = this.getAppointmentRequest(patientId, appointmentRequestId);
		
		if(SMAppointmentRequest == null)
			return SMAppointmentRequest;
		
		if(isProvider){  //If user is provider, mark as provider read all new messages
			SMAppointmentRequest.setHasProviderNewMessage(false);
		} else {
			SMAppointmentRequest.setHasVeteranNewMessage(false);
		}
        UserHistoryEntityManager<AppointmentRequestPo> simpleEntityManager = new UserHistoryEntityManager<AppointmentRequestPo>(entityManager, AppointmentRequestPo.TABLE_NAME, SMAppointmentRequest);
        AppointmentRequestPo savedAppointmentRequest = simpleEntityManager.save(new AppointmentRequestPo(SMAppointmentRequest));
		
		return savedAppointmentRequest.create();
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	public SMAppointmentRequest updateProviderSeenAppointmentRequestFlag(String patientId, String appointmentRequestId){
		SMAppointmentRequest smAppointmentRequest = this.getAppointmentRequest(patientId, appointmentRequestId);
		AppointmentRequestPo updatedAppointmentRequest = null;

        UserHistoryEntityManager<AppointmentRequestPo> simpleEntityManager = new UserHistoryEntityManager<AppointmentRequestPo>(entityManager, AppointmentRequestPo.TABLE_NAME, smAppointmentRequest);
		
		if(smAppointmentRequest != null && appointmentRequestId.equals(smAppointmentRequest.getAppointmentRequestId())){
			smAppointmentRequest.setProviderSeenAppointmentRequest(true);
			updatedAppointmentRequest = simpleEntityManager.save(new AppointmentRequestPo(smAppointmentRequest));
		}
		return updatedAppointmentRequest.create();
	}
	
	@Transactional(propagation = Propagation.REQUIRED)
	public SMAppointmentRequest updateAppointmentRequest(SMAppointmentRequest SMAppointmentRequest) {
        UserHistoryEntityManager<AppointmentRequestPo> simpleEntityManager = new UserHistoryEntityManager<AppointmentRequestPo>(entityManager, AppointmentRequestPo.TABLE_NAME, SMAppointmentRequest);
        AppointmentRequestPo mergedAppointmentRequest = simpleEntityManager.save(new AppointmentRequestPo(SMAppointmentRequest));
		return mergedAppointmentRequest.create();
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public SMAppointmentRequest saveAppointmentRequest(SMAppointmentRequest smAppointmentRequest) {
		// Second attempts will save two instances of the appointment request -
		// parent request and the second request.
		SMAppointmentRequest existingSMAppointmentRequest = this.getAppointmentRequestIfItExists(smAppointmentRequest.getAppointmentRequestId());
		if (existingSMAppointmentRequest != null) {
			smAppointmentRequest.setHasProviderNewMessage(existingSMAppointmentRequest.isHasProviderNewMessage());
			smAppointmentRequest.setHasVeteranNewMessage(existingSMAppointmentRequest.isHasVeteranNewMessage());
		}
        UserHistoryEntityManager<AppointmentRequestPo> simpleEntityManager = new UserHistoryEntityManager<AppointmentRequestPo>(entityManager, AppointmentRequestPo.TABLE_NAME, smAppointmentRequest);
        AppointmentRequestPo savedAppointmentRequest = simpleEntityManager.save(new AppointmentRequestPo(smAppointmentRequest));
        if (savedAppointmentRequest != null) {
    		return savedAppointmentRequest.create();
        }
        return null;
	}
	
	public SMAppointmentRequest getAppointmentRequestIfItExists(String appointmentRequestId) {
		AppointmentRequestPo arpo = null;
		String selectSingleAppointmentRequest = "from AppointmentRequestPo where id = :appointmentRequestId ";
		Query query = getQuery(selectSingleAppointmentRequest);
		query.setParameter("appointmentRequestId", appointmentRequestId);
		try{
			arpo = (AppointmentRequestPo) query.getSingleResult();
		} catch(NoResultException e) {
			LOGGER.debug(e);		
			arpo = null;
		} catch (NonUniqueResultException e) {
			LOGGER.debug(e);	
			arpo = null;
		}
		return (arpo == null) ? null : arpo.create();
	}

	@Transactional(propagation = Propagation.REQUIRED)
	public void deleteAppointmentRequest(SMAppointmentRequest SMAppointmentRequest) {
		ValidPo<AppointmentRequestPo> po = new ValidPo<AppointmentRequestPo>(entityManager, new AppointmentRequestPo(SMAppointmentRequest));
		if (po.isOk()) {
			AppointmentRequestPo appointmentRequestToRemove = po.getPo();
			appointmentRequestToRemove.setDeletedDate(new Date());
			appointmentRequestToRemove.setActiveFlag(false);
			this.updateAppointmentRequest(appointmentRequestToRemove.create());
		}
	}

	private Query constructWhereClause(String patientId, DateFilter filter) {
		StringBuilder whereClause = new StringBuilder();

		Map<String, Object> parameters = new HashMap<String, Object>();

		whereClause.append(" where 1=1 ");

		addPatientIdCriteria(whereClause);
		addActiveRecordCriteria(whereClause);
		addDateFilterCriteria(filter, whereClause);

		whereClause.append(" order by hasVeteranNewMessage DESC, lastUpdatedDate DESC");

		setPatientIdParameter(patientId, parameters);
		setActiveFlagParameter(parameters);
		setDataFilterParameter(filter, parameters);

		String query = "from AppointmentRequestPo " + whereClause.toString();
		Query constructedQuery = this.setParametersForQuery(query, parameters);
		return constructedQuery;
	}

	private void setPatientIdParameter(String patientId, Map<String, Object> parameters) {
		parameters.put("patientId", patientId);
	}

	private void addPatientIdCriteria(StringBuilder whereClause) {
		whereClause.append(" and patient.userId = :patientId ");
	}

	private void setDataFilterParameter(DateFilter filter, Map<String, Object> parameters) {

		if (filter == null) {
			return;
		}

		if (NullChecker.isNotNullish(filter.getStartDate())) {
			parameters.put("startDate", filter.getStartDate());
		}
		if (NullChecker.isNotNullish(filter.getEndDate())) {
			parameters.put("endDate", filter.getEndDate());
		}
	}

	private void addDateFilterCriteria(DateFilter filter, StringBuilder whereClause) {

		if (filter == null) {
			return;
		}

		if (NullChecker.isNotNullish(filter.getStartDate())) {
			whereClause.append(" and createdDate >= :startDate ");
		}
		if (NullChecker.isNotNullish(filter.getEndDate())) {
			whereClause.append(" and createdDate <= :endDate ");
		}
	}

	private Query constructWhereClause(AppointmentRequestFilter filter) {

		StringBuilder whereClause = new StringBuilder();
		Map<String, Object> parameters = new HashMap<String, Object>();

		whereClause.append(" where 1=1 ");

		addActiveRecordCriteria(whereClause);
		addFacilityNameCriteria(filter, whereClause);
		addParentSiteCodeCriteria(filter, whereClause);
		addDateFilterCriteria(filter, whereClause);
		
		whereClause.append(" order by case when hasProviderNewMessage=1 and providerSeenAppointmentRequest=1 then  hasProviderNewMessage  end DESC, lastUpdatedDate ASC");

		setActiveFlagParameter(parameters);
		setFacilityNameParameter(filter, parameters);
		setParentSiteCodeParameter(filter, parameters);
		setDataFilterParameter(filter, parameters);

		String query = "from AppointmentRequestPo " + whereClause.toString();
		Query constructedQuery = this.setParametersForQuery(query, parameters);
		return constructedQuery;
	}

	private void setFacilityNameParameter(AppointmentRequestFilter filter, Map<String, Object> parameters) {
		if (filter != null && NullChecker.isNotNullish(filter.getFacilityName())) {
			parameters.put("facilityName", filter.getFacilityName().toLowerCase());
		}
	}

    private  void setFilterStringParameter(String filterString, Map<String, Object> parameters){
        if(filterString != null && filterString.length() > 0){
            parameters.put("filterString", "%" + filterString + "%");
        }
    }
	private void setParentSiteCodeParameter(AppointmentRequestFilter filter, Map<String, Object> parameters) {
		if (filter != null && NullChecker.isNotNullish(filter.getParentSiteCode())) {
			parameters.put("parentSiteCode", filter.getParentSiteCode());
		}
	}

	private void setActiveFlagParameter(Map<String, Object> parameters) {
		parameters.put("activeFlag", true);
	}

	private void addParentSiteCodeCriteria(AppointmentRequestFilter filter, StringBuilder whereClause) {
		if (filter != null && NullChecker.isNotNullish(filter.getParentSiteCode())) {
			whereClause.append(" and facility.parentSiteCode = :parentSiteCode ");
		}
	}

	private void addFacilityNameCriteria(AppointmentRequestFilter filter, StringBuilder whereClause) {
		if (filter != null && NullChecker.isNotNullish(filter.getFacilityName())) {
			whereClause.append(" and lower(facility.name) = :facilityName ");
		}
	}

	private void addActiveRecordCriteria(StringBuilder whereClause) {
		whereClause.append(" and activeFlag = :activeFlag ");
	}

	private Query setParametersForQuery(String query, Map<String, Object> parameters) {
		LOGGER.debug(String.format(":::: Query: %s", query));
		Query constructedQuery = this.entityManager.createQuery(query);
		for (String key : parameters.keySet()) {
			constructedQuery.setParameter(key, parameters.get(key));
		}
		return constructedQuery;
	}

	private List<AppointmentRequestPo> executeForAppointmentRequests(Query query) {
		@SuppressWarnings("unchecked")
		List<AppointmentRequestPo> appointmentRequestResults = query.getResultList();

		return appointmentRequestResults;
	}

	private List<AppointmentRequestMessagePo> executeForAppointmentRequestMessages(Query query) {
		@SuppressWarnings("unchecked")
		List<AppointmentRequestMessagePo> appointmentRequestMessageResults = query.getResultList();

		return appointmentRequestMessageResults;
	}

	private AppointmentRequestPo executeForAppointmentRequest(Query query) {
		try {
			return (AppointmentRequestPo) query.getSingleResult();
		} catch (NoResultException e) {
			throw new WebApplicationException(Status.NOT_FOUND);
		} catch (NonUniqueResultException e) {
			throw new WebApplicationException(Status.PRECONDITION_FAILED);
		}
	}

	private DetailCodePo executeForDetailCode(Query query) {
		try {
			return (DetailCodePo) query.getSingleResult();
		} catch (NoResultException e) {
			throw new WebApplicationException(Status.NOT_FOUND);
		} catch (NonUniqueResultException e) {
			throw new WebApplicationException(Status.PRECONDITION_FAILED);
		}
	}

	private Query getQuery(String dbQuery) {
		Query query = this.entityManager.createQuery(dbQuery);
		return query;
	}

    private Query constructWhereClause(AppointmentRequestFilter filter, HashMap<String, Boolean> sortFields, String filterString) {

        StringBuilder whereClause = new StringBuilder();
        Map<String, Object> parameters = new HashMap<String, Object>();

        whereClause.append(" where 1=1 ");

        addActiveRecordCriteria(whereClause);
        addFacilityNameCriteria(filter, whereClause);
        addParentSiteCodeCriteria(filter, whereClause);
        addDateFilterCriteria(filter, whereClause);

        if(filterString != null && filterString.length() > 0){
            whereClause.append(" and (status LIKE :filterString " +
                    " OR appointmentType LIKE :filterString " +
                    " OR facility.name LIKE :filterString " +
                    " OR patient.lastName LIKE :filterString " +
                    " OR patient.firstName LIKE :filterString " +
                    " OR lastUpdatedDate LIKE :filterString) ");
        }

        if(sortFields == null){
            whereClause.append(" order by case when hasProviderNewMessage=1 and providerSeenAppointmentRequest=1 then  hasProviderNewMessage  end DESC, lastUpdatedDate ASC");
        }
        else{
            Iterator<String> columnIterator = sortFields.keySet().iterator();
            whereClause.append(" order by ");
            String columnName;
            String columnSeperator = "";
            while (columnIterator.hasNext()){
                columnName = (String) columnIterator.next();
                if(columnName.equalsIgnoreCase("patient.displayName")){
                    whereClause.append(columnSeperator + " concat(concat(patient.lastName, ', '),patient.firstName) " + (sortFields.get(columnName) ? " ASC " : " DESC "));
                }
                else if(columnName.equalsIgnoreCase("facility.name")){
                    whereClause.append(columnSeperator + " facility.name " + (sortFields.get(columnName) ? " ASC " : " DESC "));
                }
                else if(columnName.equalsIgnoreCase("alerts")){
                    whereClause.append(columnSeperator + " (hasProviderNewMessage || secondRequestSubmitted) " + (sortFields.get(columnName) ? " ASC " : " DESC "));
                }
                else{
                    whereClause.append(columnSeperator + columnName + " " + (sortFields.get(columnName) ? " ASC " : " DESC "));
                }
                columnSeperator = ", ";
            }
        }

        setActiveFlagParameter(parameters);
        setFacilityNameParameter(filter, parameters);
        setParentSiteCodeParameter(filter, parameters);
        setDataFilterParameter(filter, parameters);
        setFilterStringParameter(filterString, parameters);

        String query = "from AppointmentRequestPo " + whereClause.toString();
        Query constructedQuery = this.setParametersForQuery(query, parameters);
        return constructedQuery;
    }

    private boolean isCancelledAppointmentRequest(AppointmentRequestPo appointmentRequestPo) {
        if (AppointmentRequestStatus.CANCELLED.getName().equalsIgnoreCase(appointmentRequestPo.getStatus())) {
            return true;
        }
        return false;
    }
}
