/*
 * Licensed Materials - Property of IBM Corp.
 * IBM UrbanCode Release
 * (c) Copyright IBM Corporation 2014. All Rights Reserved.
 *
 * U.S. Government Users Restricted Rights - Use, duplication or disclosure restricted by
 * GSA ADP Schedule Contract with IBM Corp.
 */
package com.urbancode.urelease

import java.io.File;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.text.SimpleDateFormat;

import org.apache.log4j.Logger;

import com.urbancode.release.rest.models.internal.RelatedDeployment;
import com.urbancode.release.rest.models.internal.ScheduledDeployment;
import com.urbancode.release.rest.models.internal.ScheduledDeploymentVersion;
import com.urbancode.release.rest.models.internal.Milestone;
import com.urbancode.release.rest.models.Release;
import com.urbancode.release.rest.models.Application;
import com.urbancode.release.rest.models.Version;
import com.urbancode.release.rest.models.internal.ScheduledDeploymentVersion;
import com.urbancode.release.rest.models.internal.VersionStatus;
import com.urbancode.release.rest.models.internal.Gate;
import com.urbancode.release.rest.models.internal.ApprovalSet;
import com.urbancode.release.rest.models.internal.ApprovalItem;
import com.urbancode.urelease.UrlHelper;
import com.urbancode.commons.util.query.QueryFilter.FilterType;
import com.urbancode.release.rest.framework.QueryParams.FilterClass;
import com.urbancode.release.rest.models.Release.ChangeCount;

import java.text.DecimalFormat;
/**
 * Utilities to assist with email sending in tests and plugins.
 * @author pcentgraf
 */
public class DeploymentSummaryContextGenerator {
    protected static Logger log = Logger.getLogger(DeploymentSummaryContextGenerator.class);

    RelatedDeployment rd;
    String releaseServerUrl;
    String reportUrl;
    def releaseIds;

    public DeploymentSummaryContextGenerator(serverUrl, reportUrl) {
        releaseServerUrl = serverUrl
        this.reportUrl = reportUrl
    }

    public def generateContext(def sdIds) {
        def result = [:]

        def sdObjs = []

        def num = 0

        sdIds.each { sdId ->

            num = num + 1

            def sdObj = [:]

            def sd = new ScheduledDeployment().id(sdId);
            // sd.format("detail")
            sd = sd.getTiming()

            generateGeneralContext(sdObj, sd)

            generateOverviewContext(sdObj, sd)

            sdObjs << sdObj

            System.gc();
        }

        result["sds"] = sdObjs
        result["reportUrl"] = this.reportUrl

        addStyleContext(result)

        return result
    }

    public def queryDeployments(filterObj) {
        
        def oneWeek = 7L * 24L * 60L * 60L * 1000L;

        def endDate = filterObj.endDate != null ? filterObj.endDate : System.currentTimeMillis() + (24L * 60L * 60L * 1000L)
        def startDate = filterObj.startDate != null ? filterObj.startDate : (System.currentTimeMillis() - oneWeek)

        def releases = filterObj.releases ? filterObj.releases : null

        ScheduledDeployment query = new ScheduledDeployment();
        query.filter("phase.phaseModel.name", FilterClass.STRING, FilterType.EQUALS, "PROD");
        query.filter("scheduledDate", FilterClass.LONG, FilterType.RANGE, startDate, endDate);
        //query.filter("scheduledDate", FilterClass.LONG, FilterType.GREATER_OR_EQUAL, startDate.toString());

        if (releases) {
            query.filter("release.name", FilterClass.STRING, FilterType.IN, (String[])releases)
        }

        query.format("list");
        ScheduledDeployment[] sds = query.getAll()

        return sds;
    }

    private generateGeneralContext(obj, sd) {
        def general = [:]

        general["formattedDate"] = new Date(sd.scheduledDate).toString()
        general["name"] = sd.release.name + ": " + sd.environment.name
        general["team"] = sd.release.teamName

        // EMPTY, NOTSTARTED, INPROGRESS, SOFTFAILED, FAILED, COMPLETE, ABORTED;
        if (sd.deploymentExecution.status == "NOTSTARTED") {
            general["status"] = "Not Started"
            general["statusColor"] = "#000000"
        } else if (sd.deploymentExecution.status == "INPROGRESS") {
            general["status"] = "In Progress"
            general["statusColor"] = "#4843ba"
        } else if (sd.deploymentExecution.status == "COMPLETE") {
            general["status"] = "Completed"
            general["statusColor"] = "#27913e"
        } else if (sd.deploymentExecution.status == "FAILED") {
            general["status"] = "Failed"
            general["statusColor"] = "#a81f10"
        } else if (sd.deploymentExecution.status == "ABORTED") {
            general["status"] = "Aborted"
            general["statusColor"] = "#a81f10"
        }

        if(sd.relatedDeployment != null) {
            RelatedDeployment rd =   new RelatedDeployment().id(sd.relatedDeployment.id)
            rd.format("details")
            rd = rd.get()

            general["eventId"] = rd.id
            general["eventName"] = rd.event.name
        } else {
            general["eventId"] = null
            general["eventName"] = null
        }

        def url = new URL(new URL(releaseServerUrl), "scheduledDeployment/" + sd.id)
        general["link"] = url

        obj["general"] = general
    }

    private generateOverviewContext(obj, sd) {
        def overview = [:]

        DecimalFormat df = new DecimalFormat("#.00"); 

        def plannedDuration = sd.deploymentExecution.endTimePlanned - sd.scheduledDate
        overview["plannedDuration"] = getDurationString(plannedDuration)
        overview["plannedDurationMin"] = df.format(plannedDuration / (1000 * 60))

        if (sd.deploymentExecution.endTimeActual) {
            def actualDuration = sd.deploymentExecution.endTimeActual - sd.deploymentExecution.startTimeActual
            overview["actualDuration"] = getDurationString(actualDuration)
            overview["actualDurationMin"] = df.format(actualDuration / (1000 * 60))
        } else {
            overview["actualDuration"] = "Not Ended"
            overview["actualDurationMin"] = 0
        }

        def manualCount = 0;
        def automatedCount = 0;
        def failedCount = 0;
        def unresolvedCount = 0;

        def failuresByRole = [:]

        def failedTasks = []

        def ontimeCount = 0
        def lt10Count = 0
        def lt30Count = 0
        def lt60Count = 0
        def gt60Count = 0

        def duration_ontimeCount = 0
        def duration_lt10Count = 0
        def duration_lt30Count = 0
        def duration_lt60Count = 0
        def duration_gt60Count = 0

        sd.deploymentExecution.segments.each { segment ->
            segment.tasks.each { task -> 
                if(task.isApplicable) {
                    if(!task.automated) {
                        manualCount++
                    } else {
                        automatedCount++
                    }
                    if(task.hasFailed) {
                        failedCount++
                        if(task.result == "FAILURE") {
                            unresolvedCount++
                        }

                        def roleName = "none"
                        if(task.executorRole != null) {
                            roleName = task.executorRole.name
                        } else if (task.executorGroup != null) {
                            roleName = task.executoGroup.name
                        }

                        if (failuresByRole.containsKey(roleName)) {
                            failuresByRole[roleName] = failuresByRole[roleName] + 1
                        } else {
                            failuresByRole[roleName] = 1
                        }

                        def plannedEndTime = task.startTimePlanned + (task.duration * 60 * 1000)
                        def lateMessage = ""

                        if (task.endTimeActual != null) {
                            def lateDuration = java.lang.Math.abs(task.endTimeActual - plannedEndTime)

                            if (task.endTimeActual > plannedEndTime) {
                                lateMessage = "Finished " + getDurationString(lateDuration) + " late"
                            } else {
                                lateMessage = "Finished " + getDurationString(lateDuration) + " early"
                            }
                            
                        }

                        def failedTaskObj = [
                            "name" : task.name,
                            "owner" : task.userName != null ? task.userName : "none",
                            "status" : task.status == "CLOSED" ? task.result.toString() : task.status.toString(),
                            "lateMessage" : lateMessage
                        ]
                        failedTasks << failedTaskObj
                    }

                    def plannedEndTime = task.startTimePlanned + (task.duration * 60 * 1000)
                    if (task.endTimeActual != null) {
                        def lateDuration = java.lang.Math.abs(task.endTimeActual - plannedEndTime)
                        
                        def actualDuration = task.endTimeActual - task.startTimeActual
                        def durationDiff = actualDuration - (task.duration * 60 * 1000)

                        if (task.endTimeActual > plannedEndTime) {
                            if(lateDuration < 10 * 60 * 1000) { 
                                lt10Count++
                            } else if(lateDuration < 30 * 60 * 1000) { 
                                lt30Count++
                            } else if(lateDuration < 60 * 60 * 1000) { 
                                lt60Count++
                            } else {
                                gt60Count++
                            }
                        } else {
                            ontimeCount++
                        }

                        if(durationDiff < 0) { 
                            duration_ontimeCount++
                        } else if(durationDiff < 10 * 60 * 1000) { 
                            duration_lt10Count++
                        } else if(durationDiff < 30 * 60 * 1000) { 
                            duration_lt30Count++
                        } else if(durationDiff < 60 * 60 * 1000) { 
                            duration_lt60Count++
                        } else {
                            duration_gt60Count++
                        }
                    }
                }
            }
        }

        def lateByScheduleGraph = [
            ["\"Severity\"", "\"Tasks\""],
            ["\"On-Time or Early\"", ontimeCount],
            ["\"< 10 min late\"", lt10Count],
            ["\"< 30 min late\"", lt30Count],
            ["\"< 60 min late\"", lt60Count],
            ["\"> 60 min late\"", gt60Count]
        ]

        def tasksOverDurationGraph = [
            ["\"Severity\"", "\"Tasks\""],
            ["\"Under expected duration\"", duration_ontimeCount],
            ["\"< 10 min over\"", duration_lt10Count],
            ["\"< 30 min over\"", duration_lt30Count],
            ["\"< 60 min over\"", duration_lt60Count],
            ["\"> 60 min over\"", duration_gt60Count]
        ]

        def failuresByRoleGraphArray = [["\"Role\"", "\"Count\""]]
        failuresByRole.each { key, value ->
            failuresByRoleGraphArray << ["\"" + key + "\"", value]
        }
        
        overview["tasksOverDurationGraph"] = tasksOverDurationGraph
        overview["lateByScheduleGraph"] = lateByScheduleGraph
        overview["failuresByRoleGraph"] = failuresByRoleGraphArray.toString()
        overview["manualCount"] = manualCount
        overview["automatedCount"] = automatedCount
        overview["failedCount"] = failedCount
        overview["unresolvedCount"] = unresolvedCount
        overview["failedTasks"] = failedTasks

        obj["overview"] = overview
    }

    private getDurationString(ms) {
        def sec = (((int)(ms / 1000)) % 60)
        def min = (((int)(ms / (1000 * 60))) % 60)
        def hours = (((int)(ms / (1000 * 60 * 60))) % 60)

        if (hours > 0) {
            return hours + " hours " + min + " min"
        } else {

            return min + " min " + sec + " sec"
        }
    }

    private def addStyleContext(context) {
        context["spacerStyle"] = "height: 35px; width: 100%;"
        context["subHeadingStyle"] = "font-weight: bold;font-size: 20px;font-family: sans-serif; margin-top: 10px;"
        context["subSectionStyle"] = "margin-left: 25px; margin-top: 10px;"
        context["indentStyle"] = "margin-left: 15px;"
        context["smallHeadingStyle"] = "font-weight: bolder;font-size: 15px;font-family: sans-serif;"

        context["tableStyle"] = "border-collapse: collapse; text-align: left; width: 100%;"
        context["thStyle"] = "background:-webkit-gradient( linear, left top, left bottom, color-stop(0.05, #006699), color-stop(1, #00557F) );background:-moz-linear-gradient( center top, #006699 5%, #00557F 100% );filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#006699', endColorstr='#00557F');background-color:#006699; color:#FFFFFF; font-size: 15px; font-weight: bold; border-left: 1px solid #0070A8;"
        context["trStyle"] = "padding: 3px 10px; border-bottom: #9cb0d1 solid 1px;"
        context["tdStyle"] = "padding: 3px 10px; background: #FFFFFF; color: #00496B; border-left: 1px solid #E1EEF4;font-size: 12px;font-weight: normal;"
        context["td-altStyle"] = "padding: 3px 10px; background: #E1EEF4; color: #00496B; border-left: 1px solid #E1EEF4;font-size: 12px;font-weight: normal;"
        context["redTdStyle"] = "padding: 3px 10px; background: #ff8080; color: #00496B; border-left: 1px solid #E1EEF4;font-size: 12px;font-weight: normal;"
        context["lateOutstandingStyle"] = "clear:left; font-weight: bolder; color: #cc3300; margin-top: 5px; margin-bottom: 15px;"



        context["panelTitle"] = "text-align: center; color: #000000; margin-bottom: 10px;"

        context["lightbg"] = "background-color: #edf1f7;"
        context["darkbg"] = "background-color: #ced7e5;"

        context["leftPanelStyle"] = "float: left; font-weight: bolder; color: #cc3300; width: 50%; min-width: 200px; min-height: 300px; padding: 15px;"
        context["rightPanelStyle"] = "float: left; font-weight: bolder; color: #cc3300; width: 40%; min-width: 200px;  min-height: 300px; padding: 15px;"

        context["bodyContainer"] = "max-width: 950px; margin: auto; font-family: Arial;"
    }
}
