#!/usr/bin/env groovy
/*
 * 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.
 */

import static org.hamcrest.Matchers.*

import com.urbancode.release.rest.framework.Clients;
import java.util.Calendar
import com.google.common.base.Function
import com.google.common.collect.Maps
import com.google.common.collect.Ordering
import com.google.common.collect.Sets
import com.ibm.icu.text.DateFormat
import com.urbancode.air.*
import com.urbancode.commons.util.query.QueryFilter.FilterType
import com.urbancode.urelease.endpoint.framework.EmailUtils
import com.urbancode.urelease.ReleaseEventContextGenerator
import com.urbancode.urelease.DeploymentTrendContextGenerator
import com.urbancode.urelease.DeploymentSummaryContextGenerator
import com.urbancode.urelease.FilterObj
import com.urbancode.release.rest.framework.QueryParams.FilterClass
import com.urbancode.release.rest.models.*
import static com.urbancode.release.rest.models.internal.InternalClients.*;
import static com.urbancode.release.rest.framework.Clients.*;
import com.urbancode.release.rest.models.internal.ScheduledDeployment
import com.urbancode.release.rest.models.internal.PluginIntegrationProvider
import com.urbancode.release.rest.models.Event
import com.urbancode.release.rest.models.internal.RelatedDeployment;
//----------------------------------------------------------------------------------------------
// Extract parameters

def apTool = new AirPluginTool(this.args[0], this.args[1])
Properties props = apTool.getStepProperties()
File pluginHome = new File(System.getenv('PLUGIN_HOME'))
File templatesDir = new File(pluginHome, 'notification-templates')


File inputPropsFile = new File(props['PLUGIN_INPUT_PROPS'])

def varDir =  inputPropsFile.getParentFile().getParentFile().getParentFile()

DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.SHORT)
DateFormat timeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT)
def now = Calendar.getInstance()
def nowTimestamp = timeFormat.format(now.getTimeInMillis()).replace(" ", "_").replace(":", "-").replace("/", "-").replace(",", "_")

def reportFilePath = null

def reportName = props['reportName']
def recipients = props['recipients'].tokenize(',')
def frequency = props['frequency']
def extraPropertiesText = props['extraProperties']

def slurper = new groovy.json.JsonSlurper()

def extraProperties = null
if(extraPropertiesText) {
    extraProperties = slurper.parseText(extraPropertiesText)
}

// Used for Filtering the reports
def releases = null
def phases = null
def startDate = null
def endDate = null

if (extraProperties) {
    if (extraProperties.reportName) {
        reportName = extraProperties.reportName
    }
    if (extraProperties.recipients) {
        recipients = extraProperties.recipients.tokenize(',')
    }
    if (extraProperties.releases) {
        releases = extraProperties.releases
    }
    if (extraProperties.phases) {
        phases = extraProperties.phases
    }
    if (extraProperties.startDate) {
        startDate = extraProperties.startDate
    }
    if (extraProperties.endDate) {
        endDate = extraProperties.endDate
    }
}

def filterObj = new FilterObj(
    releases: releases,
    phases: phases,
    endDate: endDate,
    startDate: startDate
) 

if (varDir.getName() == "var") {
    File reportDir = new File(varDir, "reports")

    if( !reportDir.exists() ) {
        reportDir.mkdirs()
    }

    File reportNameDir = new File(reportDir, reportName)

    if( !reportNameDir.exists() ) {
        reportNameDir.mkdirs()
    }

    reportFilePath = reportNameDir.getAbsolutePath() + '/' + nowTimestamp + '.html'
}

def releaseServerUrl = props['releaseServerUrl']
def reportURL = releaseServerUrl + "/reports/" + reportName + "/" + nowTimestamp + ".html";

println "Now: "+timeFormat.format(now.getTimeInMillis())

def startMonth = now.clone()
startMonth.set(Calendar.DAY_OF_MONTH, 1)
midnight(startMonth)
println "Start Month: "+timeFormat.format(startMonth.getTimeInMillis())

def endMonth = startMonth.clone()
endMonth.add(Calendar.MONTH, 1)
println "End Month: "+timeFormat.format(endMonth.getTimeInMillis())

def startWeek = now.clone()
startWeek.set(Calendar.DAY_OF_WEEK, Calendar.SUNDAY)
midnight(startWeek)
println "Start Week: "+timeFormat.format(startWeek.getTimeInMillis())

def endWeek = startWeek.clone()
endWeek.add(Calendar.WEEK_OF_YEAR, 1)
println "End Week: "+timeFormat.format(endWeek.getTimeInMillis())

def today = now.clone()
midnight(today)
println "Today: "+timeFormat.format(today.getTimeInMillis())

def thirtyDays = today.clone()
midnight(thirtyDays)
thirtyDays.add(Calendar.DAY_OF_MONTH, 31)
println "Thirty Days: "+timeFormat.format(thirtyDays.getTimeInMillis())


// Get previous last-run date from prop value
Clients.loginWithToken(props['releaseServerUrl'], props['releaseToken']);
PluginIntegrationProvider provider = pluginIntegrationProvider().id(props['releaseIntegrationProvider']).get()
println "Provider props:" + provider.propertyValues.toString()

String lastRunString = provider.getProperty("lastRun-$reportName")
long lastRun = (lastRunString != null)? Long.parseLong(lastRunString): 0L

Calendar lastRunCal = Calendar.getInstance()
lastRunCal.setTimeInMillis(lastRun)
println "Last Run: "+timeFormat.format(lastRun)


// If we are not restricting the frequency of execution
if (frequency.equals("Unrestricted")
// Or this report has not been run yet
    || (lastRun == 0L)
// Or the frequency is weekly and the last run was before the current week
    || (frequency.equals("Weekly") && lastRunCal.before(startWeek))
// Or the frequency is monthly and the last run was before the current month
    || (frequency.equals("Monthly") && lastRunCal.before(startMonth))) {
// Then actually run the report

    Map<String,Object> context
    def isSendMultiple = false;
    def contexts = []

    if(reportName == "ReleaseEventReadinessReport") {

        // Scarb Demo
        def relatedDeployments = queryForReleaseEvents();

        isSendMultiple = true

        for(def relatedDeployment : relatedDeployments) {
            RelatedDeployment rd = relatedDeployment.get()

            ReleaseEventContextGenerator recGenerator = new ReleaseEventContextGenerator(rd, releaseServerUrl)
            contexts << recGenerator.generateContext()
        }

        println "<b style='font-weight: bold; text-decoration: underline;'>Sending Emails For " + contexts.size() + " Release Events</b>"

    } else if(reportName == "DeploymentTrendReport") {


        //context = recGenerator.generateContext()

        def planNameMap = DeploymentTrendContextGenerator.queryForDeployments(filterObj);
        DeploymentTrendContextGenerator dtGenerator = new DeploymentTrendContextGenerator(planNameMap, releaseServerUrl, reportURL)
        context = dtGenerator.generateContext()

    }  else if(reportName == "DeploymentSummaryReport") {


        //context = recGenerator.generateContext()

        // def planNameMap = DeploymentSummaryContextGenerator.queryForDeployments();
        DeploymentSummaryContextGenerator generator = new DeploymentSummaryContextGenerator(releaseServerUrl, reportURL)
        def sds = generator.queryDeployments(filterObj)
        def sdIds = [];

        sds.each { sd ->
            sdIds << sd.id
        }

        context = generator.generateContext(sdIds)

    } else {
        // Store new last-run date (now) as a prop value
        provider.property("lastRun-$reportName", Long.toString(now.getTimeInMillis())).save()

        // Load data from REST services
        ScheduledDeployment[] weekProdSDs = getProdSDs(startWeek.getTimeInMillis(), endWeek.getTimeInMillis(), filterObj)
        ScheduledDeployment[] thirtyDaysProdSDs = getProdSDs(today.getTimeInMillis(), thirtyDays.getTimeInMillis(), filterObj)
        Event[] blackoutEvents = event()
            .filter("startDate", FilterClass.LONG, FilterType.GREATER_OR_EQUAL, today.getTimeInMillis())
            .filter("endDate", FilterClass.LONG, FilterType.LESS_THAN, thirtyDays.getTimeInMillis())
            .like("type.name", "Blackout")
            .when().getAll()

        def weeklySDObjs = []

        for(sd in weekProdSDs) {

            def teamName = sd.release.team.name;
            def lifecycleName = sd.release.lifecycleModel.name;
            def applicationTeams= sd.release.applicationTeamNames;
            def description = sd.release.description

            sd = sd.get();

            def appVersions = ""

            if(sd.versions) {
                appVersions = sd.versions.length + " Application Versions"
            }

            sdObj = new DeploymentRow(
                lifecycleName: lifecycleName,
                teamName: teamName,
                release: sd.release,
                releaseName: sd.release.name,
                applicationTeams: applicationTeams,
                startTime: sd.scheduledDate,
                description: description,
                status: sd.deploymentExecution.status,
                appVersions: appVersions,
                aheadBehind: "",
                aheadBehindStyle: "",
                scheduledDate: sd.scheduledDate,
                deploymentExecution: sd.deploymentExecution
            )

            determinePercentAutomated(sd, sdObj)
            determineAheadBehind(sd, sdObj)

            weeklySDObjs << sdObj

        }

        // Prep template parameters and utilities
        context = [
            EmailUtils : EmailUtils.class,
            dateFormat : dateFormat,
            timeFormat : timeFormat,
            startWeekDate : dateFormat.format(startWeek.getTimeInMillis()),
            endWeekDate : dateFormat.format(endWeek.getTimeInMillis()),
            todayDate : dateFormat.format(today.getTimeInMillis()),
            thirtyDaysDate : dateFormat.format(thirtyDays.getTimeInMillis()),
            weeklyDeployments : weeklySDObjs,
            thirtyDaysDeployments : thirtyDaysProdSDs,
            blackoutEvents : blackoutEvents,
            tableStyle : "style=\"width:100%; margin-left:auto; margin-right:auto; background-color:#FFFFFF; border-spacing: 0; font-family: helvetica neue, arial, sans-serif; font-weight: 400; font-size: 14px; text-align: left;\"",
            thStyle : "style=\"color: #FFF; background-color: #008ABF; font-weight: 400; padding: 8px 6px;\"",
            tdTopStyle : "style=\"padding: 8px 6px; vertical-align: top;\"",
            tdStyle : "style=\"border-bottom: 1px solid #D0D2D3; padding: 8px 6px; vertical-align: top;\"",
            majorStyle : "style=\"background-color: yellow;\""
        ]

    }

    // Send the actual email, by applying the selected report template
    EmailUtils.startEmailServices(pluginHome, props)

    if(isSendMultiple) {
        for(def c : contexts) {
            def body = EmailUtils.sendEmail(templatesDir, reportName, c, recipients, [])

            if(reportFilePath != null) {
                def reportFile = new File(reportFilePath)
                reportFile.newWriter().withWriter { w ->
                    w << body
                }

                println "<b>Printed Copy of Report to Tomcat Static Dir</b>"
                println "Report accessible here: <a target='_blank' href='" + reportURL + "'/>" + reportURL + "</a>"
            }
        }
    } else {
        def body = EmailUtils.sendEmail(templatesDir, reportName, context, recipients, [])

        if(reportFilePath != null) {
            def reportFile = new File(reportFilePath)
            reportFile.newWriter().withWriter { w ->
                w << body
            }

            println "<b>Printed Copy of Report to Tomcat Static Dir</b>"
            println "Report accessible here: <a target='_blank' href='" + reportURL + "'/>" + reportURL + "</a>"
        }
    }

    EmailUtils.stopEmailServices()
}
else {
    println "NO EMAIL -- Execution suppressed by frequency parameter"
}


//----------------------------------------------------------------------------------------------
// Helper method for setting a calendar's time values to midnight (all zeros)
def Calendar midnight(Calendar cal) {
    cal.with {
        set(Calendar.HOUR_OF_DAY, 0)
        set(Calendar.MINUTE, 0)
        set(Calendar.SECOND, 0)
        set(Calendar.MILLISECOND, 0)
    }
}


//----------------------------------------------------------------------------------------------
// Helper method for loading deployment data
def ScheduledDeployment[] getProdSDs(long startInclusive, long endExclusive, FilterObj filterObj) {
    // Helpers for extracting data from and sorting deployments
    def Function<ScheduledDeployment,String> GET_TEAM =
            new Function<ScheduledDeployment,String>() {
        public String apply(ScheduledDeployment sd) {
            return sd.release.team.name
        }
    }

    def Function<ScheduledDeployment,Long> GET_SCHEDULED_START =
            new Function<ScheduledDeployment,Long>() {
        public Long apply(ScheduledDeployment sd) {
            return sd.scheduledDate
        }
    }

    def Ordering<ScheduledDeployment> BY_TEAM_AND_SCHEDULED_DATE =
            Ordering.from(String.CASE_INSENSITIVE_ORDER).nullsLast().onResultOf(GET_TEAM)
            .compound(Ordering.natural().nullsLast().onResultOf(GET_SCHEDULED_START))

    // XXX: filters do not appear to be working!
    ScheduledDeployment sd = scheduledDeployment()
    
    sd.filter("scheduledDate", FilterClass.LONG, FilterType.RANGE, startInclusive, endExclusive)
        // XXX: switch to equals when this works across joins
    sd.like("phase.phaseModel.name", "PROD")
    sd.format("list")

    if(filterObj.releases) {
        sd.filter("release.name", FilterClass.STRING, FilterType.IN, (String[])filterObj.releases)
    }    
        
    ScheduledDeployment[] sds = sd.getAll()

     Collections.sort(Arrays.asList(sds), BY_TEAM_AND_SCHEDULED_DATE)
     return sds
}

def determinePercentAutomated(sd, sdObj) {
    def totalTasks  = 0
    def automatedTasks = 0

    for (segment in sd.deploymentExecution.segments) {
        for (task in segment.tasks) {
            if (task.isApplicable) {
                totalTasks += 1
                if (task.automated) {
                    automatedTasks += 1
                }
            }
        }
    }

    def percent = 0

    if (totalTasks > 0) {
        percent = 100 * (automatedTasks / totalTasks)
    }

    sdObj["percentAutomation"] = automatedTasks + "/" + totalTasks + " tasks are automated (" + percent + "%)";


}

def determineAheadBehind(sd, sdObj) {
    sd.format("relatedDeployment")
    sd = sd.get()
    def endTimePlanned = sd.deploymentExecution.endTimePlanned
    def endTimeActual = sd.deploymentExecution.endTimeActual
    if (endTimeActual == null) {
        return
    }
    def startTimeActual = sd.deploymentExecution.startTimeActual
    def scheduledDate = sd.scheduledDate

    if(endTimePlanned == null || scheduledDate == null) {
        return
    }

    def plannedDuration = endTimePlanned - scheduledDate
    def actualDuration = endTimeActual - startTimeActual

    def behindDiff = actualDuration - plannedDuration

    def behindMin = behindDiff / (1000 * 60)

    def message = "Lasted " + behindMin + " min longer than expected"
    def style = "style=\"color: darkred;\""
    if(behindMin < 0) {
        message = "Finished " + (-1 * behindMin) + " min shorter than expected"
        style = "style=\"color: green;\""
    }

    sdObj["endTime"] = endTimeActual
    sdObj["aheadBehind"] = message
    sdObj["aheadBehindStyle"] = style
}

def queryForReleaseEvents() {

    def now = Calendar.getInstance();
    def today = now.clone()
    def sevenDaysFromNow = today.getTimeInMillis() + 7L * 24L * 60L * 60L * 1000L;
    def result = []

    Event[] events = event()
            .filter("startDate", FilterClass.LONG, FilterType.GREATER_OR_EQUAL, today.getTimeInMillis())
            // .filter("startDate", FilterClass.LONG, FilterType.LESS_THAN, sevenDaysFromNow)
            .when().getAll()

    for (Event event : events) {
        if(event.relatedDeployment != null && event.startDate < sevenDaysFromNow) {
            result << event.relatedDeployment
        }
    }

    return result;
}

class DeploymentRow {
    def teamName;
    def releaseName;
    def applicationTeams;
    def startTime;
    def endTime;
    def description;
    def status;
    def aheadBehind;
    def percentAutomation;
    def lifecycleName;
    def endTimeOverride;
    def aheadBehindStyle;
    def appVersions;
    def release;
    def deploymentExecution;
    def scheduledDate;
}