package com.agilex.vamf.missionhealth.datalayer;

import com.agilex.vamf.missionhealth.domain.*;
import com.agilex.vamf.missionhealth.domain.enumeration.MissionStatus;
import com.agilex.vamf.missionhealth.message.EmailDefaults;
import com.agilex.vamf.missionhealth.message.EmailNotificationPublisher;
import com.agilex.vamf.missionhealth.message.EmailNotificationMessage;
import com.agilex.vamf.missionhealth.service.*;
import com.agilex.vamf.missionhealth.service.enumeration.NotificationType;
import com.agilex.vamf.utils.DateHelper;
import com.agilex.vamf.utils.PropertyHelper;
import com.mongodb.DBCursor;

import javax.annotation.Resource;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: vguthrie
 * Date: 12/12/13
 * Time: 10:19 AM
 *
 * All scheduled methods should go here
 */
public class MissionHealthTaskService {
    private static final org.apache.commons.logging.Log logger = org.apache.commons.logging.LogFactory.getLog(MissionHealthTaskService.class);

    private static final int CRITICAL_MISSION_INTERVAL = 50;
    private static final int MISSION_READINESS_RANK_CHECK=3;

    private static final Integer LOSING_TROOPS_FOR_POINTS=50;
    private static final Integer LOSING_TROOPS_FOR_RANK=8;
    private static final Integer LOSING_TROOPS_FOR_FAILEDMISSION=3;
    private static final Integer MIN_TROOPS=1;

    @Resource
    MissionHealthDataLayer missionHealthDataLayer;

    @Resource
    PointService pointService;

    @Resource
    EmailNotificationPublisher publisher;

    @Resource
    private PropertyHelper propertyHelper;

    /* Scheduled methods */
    public void triggerWeeklyTasks() {
        missionHealthDataLayer.triggerWeeklyTasks();
    }

    public void triggerDailyReminder(){
        missionHealthDataLayer.triggerNotificationTask(NotificationType.EMAIL_REMINDER_TYPE_DAILY.toString());
    }

    public void triggerMidWeekReminder(){
        missionHealthDataLayer.triggerNotificationTask(NotificationType.EMAIL_REMINDER_TYPE_MID_WEEK.toString());
    }

    public void triggerEndWeekReminder(){
        missionHealthDataLayer.triggerNotificationTask(NotificationType.EMAIL_REMINDER_TYPE_END_WEEK.toString());
    }

    public void runWeeklyTasks(){
        try {
            MissionHealthWeeklyStatus missionHealthWeeklyStatus = missionHealthDataLayer.startWeeklyTasks();
            if(null!=missionHealthWeeklyStatus)
                missionHealthDataLayer.saveMissionHealthWeeklyStatus(runWeeklyTasks(missionHealthWeeklyStatus));
        } catch (Throwable t) {
            logger.error("Could not start weekly tasks", t);
        }
    }

    public void runNotificationTasks(){
        try {
            /* check daily email */
            MissionHealthNotificationStatus notificationStatus = missionHealthDataLayer.startNotificationTask(NotificationType.EMAIL_REMINDER_TYPE_DAILY.toString());
            if(null!=notificationStatus)
                missionHealthDataLayer.saveNotificationStatus(sendEmailNotification(notificationStatus, NotificationType.EMAIL_REMINDER_TYPE_DAILY.toString(), "daily"));

            /* check mid-week email */
            notificationStatus = missionHealthDataLayer.startNotificationTask(NotificationType.EMAIL_REMINDER_TYPE_MID_WEEK.toString());
            if(null!=notificationStatus)
                missionHealthDataLayer.saveNotificationStatus(sendEmailNotification(notificationStatus, NotificationType.EMAIL_REMINDER_TYPE_MID_WEEK.toString(), "midweek"));

            /* check end-week email */
            notificationStatus = missionHealthDataLayer.startNotificationTask(NotificationType.EMAIL_REMINDER_TYPE_END_WEEK.toString());
            if(null!=notificationStatus)
                missionHealthDataLayer.saveNotificationStatus(sendEmailNotification(notificationStatus, NotificationType.EMAIL_REMINDER_TYPE_END_WEEK.toString(), "endweek"));
        } catch (Throwable t) {
            logger.error("Could not start weekly tasks", t);
        }
    }

    /* helper methods */
    /**
     * This method is configured as a weekly scheduled task to handle the following responsibilities:
     *
     *  A. Aggregate data for CDW export
     *  B. Mission Completion Logic
     *      1) Determines status of all missions from the most recent cycle (previous week)
     *      2) Sets points won/forfeited based on the status of the mission
     *      3) Moves current missions to 'past' missions, clears current missions
     *  C. Promotion Logic
     *      1) Determines if rank should be updated based on total missions completed
     *  D. Gaining/Losing Troops
     *
     */
    public MissionHealthWeeklyStatus runWeeklyTasks(MissionHealthWeeklyStatus missionHealthWeeklyStatus) {
        boolean failure = false;
        int detailRecordsUpdated = 0;
        List<String> errors = new ArrayList<String>();
        Date weeklyTasksStartDatetime  = missionHealthWeeklyStatus.getStartDatetime();
        try {
            Map<String,Calendar> missionYearTimeFrame=MissionHealthTimeFrameService.getMissionYearTimeFrame(DateHelper.getToday());
        	/* Aggregate Weekly Metrics */
            missionHealthWeeklyStatus = missionHealthDataLayer.getMetricsForAllUsers(missionHealthWeeklyStatus);
            missionHealthWeeklyStatus.setServiceMetrics(missionHealthDataLayer.getMetricsByService());

            /* Mission Completion + Promotion Logic */
            Map<Integer, PointsRule> pointsRuleMap = missionHealthDataLayer.getPointsRuleMapping();
            Map<Integer, Map<String, MissionHealthRank>> missionHealthRankMap = missionHealthDataLayer.getMissionHealthRankMapping();
            DBCursor detailsCursor = missionHealthDataLayer.getWeeklyJobCursor();
            MissionHealthTranslator translator = new MissionHealthTranslator();

            /* clear weekly standing */
            missionHealthDataLayer.clearWeeklyStanding();

            while(detailsCursor.hasNext()){
                MissionHealthDetail missionHealthDetail = translator.translateToMissionHealthDetail(detailsCursor.next());
                /* cannot promote without a profile, rank, but there will be details objects where this is a valid scenario */
                if(null!=missionHealthDetail.getMissionHealthProfile() && null!=missionHealthDetail.getRank()){
                    Integer pointsEarnedForCycle = 0;
                    Integer missionsCompletedForCycle = 0;
                    Integer missionsAttemptedForCycle=0;
                    Integer totalCurrentMission=0;
                    List<Mission> currentMissions = missionHealthDetail.getCurrentMissions();
                    List<Mission> tempAttemptedMissions=new ArrayList<Mission>();;
                    if(currentMissions!=null){
                        totalCurrentMission=currentMissions.size();
                    }
                    if(totalCurrentMission>0){
                        missionHealthDetail.setInactiveCycles(0);
                    }else if (missionHealthDetail.getTotalMissions()>0){
                        missionHealthDetail.setInactiveCycles(missionHealthDetail.getInactiveCycles()+1);
                    }
                    /* evaluate mission completion */
                    for(Mission mission : currentMissions){
                        if(mission.getStartDate().after(missionHealthWeeklyStatus.getStartDatetime()))
                            continue; // exclude missions created after process started running
                        Float percentComplete = Float.valueOf(null!=mission.getPercentageCompleted()?mission.getPercentageCompleted().replace("%",""):"0");
                        Float thresholdPercent = Float.valueOf(mission.getThresholdPercentage().replace("%", ""));
                        if(percentComplete>=thresholdPercent){
                            mission.setStatus(MissionStatus.COMPLETE);
                            mission.setPointsWon(mission.getTotalPoints());
                            missionsCompletedForCycle++;
                            pointsEarnedForCycle+=mission.getTotalPoints();
                        }else{
                            int pointsWon = Math.round((percentComplete/100.0F)*(float)mission.getTotalPoints());
                            mission.setPointsWon(pointsWon);
                            mission.setPointsForfeited(mission.getTotalPoints()-pointsWon);
                            mission.setStatus(MissionStatus.ATTEMPTED);
                            pointsEarnedForCycle+=pointsWon;
                            missionsAttemptedForCycle++;
                            tempAttemptedMissions.add(mission);
                        }
                        mission.setShowMessage(true);
                        missionHealthDetail.getMissions().add(mission);
                    }
                    missionHealthDetail.getCurrentMissions().clear();
                    missionHealthDetail.setPoints(missionHealthDetail.getPoints()+pointsEarnedForCycle);
                    missionHealthDetail.setTotalCompletedMissions(missionHealthDetail.getTotalCompletedMissions()+missionsCompletedForCycle);
                    missionHealthDetail.setTotalAttemptedMissions(missionHealthDetail.getTotalAttemptedMissions()+missionsAttemptedForCycle);
                    if(DateHelper.formatDate(DateHelper.getToday()).equals(DateHelper.formatDate(missionYearTimeFrame.get("startDate").getTime()))){
                        missionHealthDetail.setAnnualTotalCompletedMissions(0);
                        missionHealthDetail.setAnnualTotalPoints(0);
                        if(missionHealthDetail.getAnnualTotalTroops()>=MIN_TROOPS)
                        {missionHealthDetail.setAnnualTotalTroops(MIN_TROOPS);}
                    }
                    else{
                        missionHealthDetail.setAnnualTotalPoints(missionHealthDetail.getAnnualTotalPoints()+pointsEarnedForCycle);
                        missionHealthDetail.setAnnualTotalCompletedMissions(missionHealthDetail.getAnnualTotalCompletedMissions()+missionsCompletedForCycle);
                    }
                    /* update weekly metrics */
                    missionHealthWeeklyStatus.setTotalMissionsCompleted(missionHealthWeeklyStatus.getTotalMissionsCompleted()+missionsCompletedForCycle);
                    WeeklyServiceMetrics serviceMetrics = new WeeklyServiceMetrics(missionHealthDetail.getMissionHealthProfile().getService());
                    int pos = missionHealthWeeklyStatus.getServiceMetrics().indexOf(serviceMetrics);
                    if(pos >= 0){
                        serviceMetrics = missionHealthWeeklyStatus.getServiceMetrics().get(pos);
                        serviceMetrics.setTotalMissionsCompleted(serviceMetrics.getTotalMissionsCompleted()+missionsCompletedForCycle);
                        serviceMetrics.setTotalPoints(serviceMetrics.getTotalPoints()+pointsEarnedForCycle);
                        missionHealthWeeklyStatus.getServiceMetrics().set(pos, serviceMetrics);
                    }else{
                        serviceMetrics.setTotalMissionsCompleted(serviceMetrics.getTotalMissionsCompleted()+missionsCompletedForCycle);
                        serviceMetrics.setTotalPoints(serviceMetrics.getTotalPoints() + pointsEarnedForCycle);
                        missionHealthWeeklyStatus.getServiceMetrics().add(serviceMetrics);
                    }
                    for(Mission mission : tempAttemptedMissions){
                    	 serviceMetrics = new WeeklyServiceMetrics(mission.getCompetingService());
                         pos = missionHealthWeeklyStatus.getServiceMetrics().indexOf(serviceMetrics);
                         if(pos >= 0){
                             serviceMetrics = missionHealthWeeklyStatus.getServiceMetrics().get(pos);
                             serviceMetrics.setTotalPoints(serviceMetrics.getTotalPoints()+mission.getPointsForfeited());
                             missionHealthWeeklyStatus.getServiceMetrics().set(pos, serviceMetrics);
                         }
                         else{
                        	 serviceMetrics.setTotalPoints(serviceMetrics.getTotalPoints()+mission.getPointsForfeited());
                             missionHealthWeeklyStatus.getServiceMetrics().set(pos, serviceMetrics);
                         }
                    }
                    WeeklyUserMetrics userMetrics = new WeeklyUserMetrics(missionHealthDetail.getPatientId(), weeklyTasksStartDatetime);
                    userMetrics.setMissionReadinessReportList(missionHealthDetail.getMissionReadinessReports());
                    userMetrics.setDaysWithLoginForCycle(missionHealthDetail.getTotalLoginDaysForCycle());
                    missionHealthDataLayer.saveWeeklyUserMetrics(userMetrics);

                    /* determine promotion */
                    PointsRule currentLevelRule = pointsRuleMap.get(missionHealthDetail.getRank().getLevel());
                    Integer nextLevel = missionHealthDetail.getRank().getLevel()+1;
                    PointsRule nextLevelRule = pointsRuleMap.get(nextLevel);
                    if(null!=nextLevelRule && missionsCompletedForCycle>0 && missionHealthDetail.getTotalCompletedMissions() >= nextLevelRule.getMissionsToAchieveLevel()){
                        MissionHealthRank nextRank = missionHealthRankMap.get(nextLevel).get(missionHealthDetail.getMissionHealthProfile().getService());
                        if(null != nextRank) {
                            missionHealthDetail.setRankEffectiveDate(new Date());
                            if(nextLevel%MISSION_READINESS_RANK_CHECK==0){
                                missionHealthDetail.setPendingRank(nextRank);
                            } else {
                                missionHealthDetail.setPromoted(true);
                                missionHealthDetail.setPreviousRank(missionHealthDetail.getRank());
                                missionHealthDetail.setRank(nextRank);
                            }
                        }
                    }
                    List<Mission> cleanedPastMissions = new ArrayList<Mission>();
                    for(Mission mission : missionHealthDetail.getMissions()){
                        if(mission.getStartDate().after(DateHelper.minusDays(DateHelper.getOneYearAgo(), 7))){
                            cleanedPastMissions.add(mission);
                        }
                    }
                    missionHealthDetail.setMissions(cleanedPastMissions);

                    /**
                     * Troops logic
                     */
                    if(currentLevelRule!=null && currentLevelRule.getId()>0 &&
                            totalCurrentMission>0 && missionsAttemptedForCycle==totalCurrentMission){
                        missionHealthDetail.setFailedMissionCounter(missionHealthDetail.getFailedMissionCounter()+1);
                    } else if(totalCurrentMission!=0){
                        missionHealthDetail.setFailedMissionCounter(0);
                    }

                    /*
                     * Gaining troops
                     * When promoted to rank 1 for first time gets 2 troops
                     */
                    if(totalCurrentMission>0){
                        if(nextLevelRule!=null && nextLevelRule.getId()==1 && missionHealthDetail.isPromoted() && missionHealthDetail.getTroops()==0){
                            missionHealthDetail.setTroops(nextLevelRule.getTroopsAllocated());
                            missionHealthDetail.setAnnualTotalTroops(nextLevelRule.getTroopsAllocated());
                        }
                        else if(currentLevelRule.getId()>=1 && pointsEarnedForCycle>=currentLevelRule.getPointsForNewTroop()){
                            missionHealthDetail.setTroops(missionHealthDetail.getTroops()+currentLevelRule.getTroopsAllocated());
                            missionHealthDetail.setAnnualTotalTroops(missionHealthDetail.getAnnualTotalTroops()+currentLevelRule.getTroopsAllocated());
                        }

                        /*
	                     * Losing troops
	                     */
                        boolean troopsLost=false;
                        if(currentLevelRule.getId()>=LOSING_TROOPS_FOR_RANK && !(missionHealthDetail.getTroops()<=MIN_TROOPS)){
                            for(Mission mission : tempAttemptedMissions){
                                int index=missionHealthDetail.getMissions().indexOf(mission);
                                mission=missionHealthDetail.getMissions().get(index);
                                if(mission.getPointsWon()<LOSING_TROOPS_FOR_POINTS){
                                    missionHealthDetail.setTroops(missionHealthDetail.getTroops()-1);
                                    Integer annualTroopsLeft=missionHealthDetail.getAnnualTotalTroops()-1;
                                    missionHealthDetail.setAnnualTotalTroops(annualTroopsLeft<=0?1:annualTroopsLeft);
                                    mission.setTroopsForfeited(mission.getTroopsForfeited()+1);
                                    missionHealthDetail.getMissions().set(index, mission);
                                    troopsLost=true;
                                }
                                if(missionHealthDetail.getTroops()<=MIN_TROOPS){
                                    break;
                                }
                            }
                        }
                        if(currentLevelRule.getId()>=1 && missionHealthDetail.getFailedMissionCounter()>=LOSING_TROOPS_FOR_FAILEDMISSION && !(missionHealthDetail.getTroops()<=MIN_TROOPS)){
                        	Integer troops=0;
	                    	/**
	                    	 * 3rd missed/failed mission cycle loses 2 troops.
	                    	 * From 4th missed/failed mission cycle loses 1 troop.
	                    	 */
	                    	if(missionHealthDetail.getFailedMissionCounter()==LOSING_TROOPS_FOR_FAILEDMISSION){
	                    		troops=missionHealthDetail.getTroops()-2;
	                    		missionHealthDetail.setTroops(troops<=0?1:troops);
		                        troopsLost=true;
		                        troops=missionHealthDetail.getAnnualTotalTroops()-2;
		                        missionHealthDetail.setAnnualTotalTroops(troops<=0?1:troops);
	                        }
	                        else{
	                        	missionHealthDetail.setTroops(missionHealthDetail.getTroops()-1);
		                        troopsLost=true;
		                        troops=missionHealthDetail.getAnnualTotalTroops()-1;
		                        missionHealthDetail.setAnnualTotalTroops(troops<=0?1:troops);	
	                        }
                        }
                        /**
                         * Troops Notification
                         */
                        if(troopsLost){
                            if(missionHealthDetail.getMissionHealthProfile().getReminder().equalsIgnoreCase("None")){
                                missionHealthDetail.setLostTroopsShowMessage(true);
                            }
                            else{
                                sendEmailNotification(missionHealthDetail, "losingTroop");
                            }
                        }
                    }
                    /**
                     * End Troops logic
                     */
                }
                missionHealthDetail.setTotalLoginsForCycle(0);
                missionHealthDetail.setTotalLoginDaysForCycle(0);
                missionHealthDetail = missionHealthDataLayer.updateMissionHealthDetail(missionHealthDetail);
                if(null!=missionHealthDetail.getMissionHealthProfile() && missionHealthDetail.hasRecentMissions()){
                    missionHealthDataLayer.updateWeeklyStanding(missionHealthDetail);
                    missionHealthDetail.getMissionHealthProfile().setServiceLastRankedAs(missionHealthDetail.getMissionHealthProfile().getService());
                }

                detailRecordsUpdated++;
            }
            /**
             * If: Its a new year for mission health so annualServiceMetrics goes back to zero
             * Else: Calculate values for annualServiceMetrics
             */
            if(DateHelper.formatDate(DateHelper.getToday()).equals(DateHelper.formatDate(missionYearTimeFrame.get("startDate").getTime()))){
                List<WeeklyServiceMetrics> annualServiceMetrics=new ArrayList<WeeklyServiceMetrics>();
                for (com.agilex.vamf.missionhealth.domain.Service service : com.agilex.vamf.missionhealth.domain.Service.values()) {
                    WeeklyServiceMetrics wsm=new WeeklyServiceMetrics(service.getService());
                    annualServiceMetrics.add(wsm);
                }
                missionHealthWeeklyStatus.setAnnualServiceMetrics(annualServiceMetrics);
            }else{
                missionHealthWeeklyStatus.setAnnualServiceMetrics(missionHealthDataLayer.getAnnualMetricsByService(DateHelper.formatDateTimeInVistaFormat(missionYearTimeFrame.get("startDate").getTime())));
            }
        } catch (Throwable t) {
            failure = true;
            logger.error("Error thrown while running weekly tasks", t);
            errors.add(generateErrorString(t));
        } finally {
            missionHealthWeeklyStatus.setComplete(!failure);
            missionHealthWeeklyStatus.setStatus(failure?MissionHealthWeeklyStatus.FAILED:MissionHealthWeeklyStatus.SUCCESS);
            missionHealthWeeklyStatus.setCompleteDatetime(new Date());
            missionHealthWeeklyStatus.setDetailRecordsUpdated(detailRecordsUpdated);
            missionHealthWeeklyStatus.setErrors(errors);
        }
        return missionHealthWeeklyStatus;
    }

    private String generateErrorString(Throwable t){
        StringBuilder sb = new StringBuilder();
        if(null!=t.getMessage()){
            sb.append("Exception thrown: ").append(t.getMessage());
        }
        if(null!=t.getCause()){
            sb.append("Cause: ").append(t.getCause().getMessage());
        }
        if(null!=t.getStackTrace()){
            sb.append("Trace: \n");
            StackTraceElement[] trace = t.getStackTrace();
            for(StackTraceElement element : trace){
                sb.append(element.toString()).append("\n");
            }
        }
        return sb.toString();
    }

    private MissionHealthNotificationStatus sendEmailNotification(MissionHealthNotificationStatus notificationStatus, String reminderType, String propertyType) {
        boolean failure = false;
        try{
            DBCursor detailsCursor = missionHealthDataLayer.getWeeklyJobCursor();
            MissionHealthTranslator translator = new MissionHealthTranslator();
            String appUri="";
            if(propertyHelper.getProperty("oauth.authorize_url")!=null){
                appUri=propertyHelper.getProperty("oauth.authorize_url");
                appUri="\nPlease go to "+appUri.substring(0,appUri.indexOf("AuthorizationServices"))+"mission-health/ to report your progress.\n";
            }
            EmailNotificationMessage message;
            while(detailsCursor.hasNext()){
                MissionHealthDetail missionHealthDetail = translator.translateToMissionHealthDetail(detailsCursor.next());
                if(null== missionHealthDetail.getMissionHealthProfile()){
                    continue; // cannot email without a profile
                }
                if(missionHealthDetail.getMissionHealthProfile().getReminder().contains(reminderType)){
                    message = new EmailNotificationMessage(
                            missionHealthDetail.getMissionHealthProfile().getEmail(),
                            new EmailDefaults(propertyHelper.getProperty(propertyType+".emailFrom"),
                                    propertyHelper.getProperty(propertyType+".emailTo"),
                                    propertyHelper.getProperty(propertyType+".emailSubject").replaceAll("\\(INSERT RANK\\)", missionHealthDetail.getRank().getTitle()).replaceAll("\\(INSERT LAST NAME\\)", missionHealthDetail.getMissionHealthProfile().getLastName()),
                                    propertyHelper.getProperty(propertyType+".emailBody").replaceAll("\\(INSERT RANK\\)", missionHealthDetail.getRank().getTitle()).replaceAll("\\(INSERT LAST NAME\\)", missionHealthDetail.getMissionHealthProfile().getLastName())
                                            +appUri+propertyHelper.getProperty("email.signature")+"\n Date Created:"+DateHelper.getToday()));

                    publisher.send(message);

                    logger.debug("Email Notification Message sent");
                }
            }
        } catch(Exception e){
            failure = true;
            logger.error("Error getting computername");
        } finally {
            notificationStatus.setComplete(!failure);
            notificationStatus.setStatus(failure?MissionHealthNotificationStatus.FAILED:MissionHealthNotificationStatus.SUCCESS);
        }
        return notificationStatus;
    }

    private void sendEmailNotification(MissionHealthDetail missionHealthDetail,String propertyType) {
        EmailNotificationMessage message =null;
        if(null!= missionHealthDetail.getMissionHealthProfile()){
            message = new EmailNotificationMessage(
                    missionHealthDetail.getMissionHealthProfile().getEmail(),
                    new EmailDefaults(propertyHelper.getProperty(propertyType+".emailFrom"),
                            propertyHelper.getProperty(propertyType+".emailTo"),
                            propertyHelper.getProperty(propertyType+".emailSubject"),
                            propertyHelper.getProperty(propertyType+".emailBody")
                                    +propertyHelper.getProperty("email.signature")+"\n Date Created:"+DateHelper.getToday()));

            publisher.send(message);
            logger.debug("Email Notification Message sent");
        }
    }
}
