// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
// either express or implied. See the License for the specific language governing
// permissions and limitations under the License.

// Package contracts helps persist documents state to disk
package contracts

import (
	"fmt"

	"github.com/aws/amazon-ssm-agent/agent/log"
	"github.com/aws/amazon-ssm-agent/agent/times"
)

// TODO move part of the function to service?
// prepareRuntimeStatus creates the structure for the runtimeStatus section of the payload of SendReply
// for a particular plugin.
func prepareRuntimeStatus(log log.T, pluginResult PluginResult) PluginRuntimeStatus {
	var resultAsString string

	if pluginResult.Error == "" {
		resultAsString = fmt.Sprintf("%v", pluginResult.Output)
	} else {
		resultAsString = pluginResult.Error
	}

	runtimeStatus := PluginRuntimeStatus{
		Code:           pluginResult.Code,
		Name:           pluginResult.PluginName,
		Status:         pluginResult.Status,
		Output:         resultAsString,
		StartDateTime:  times.ToIso8601UTC(pluginResult.StartDateTime),
		EndDateTime:    times.ToIso8601UTC(pluginResult.EndDateTime),
		StandardOutput: pluginResult.StandardOutput,
		StandardError:  pluginResult.StandardError,
	}

	if pluginResult.OutputS3BucketName != "" {
		runtimeStatus.OutputS3BucketName = pluginResult.OutputS3BucketName
		if pluginResult.OutputS3KeyPrefix != "" {
			runtimeStatus.OutputS3KeyPrefix = pluginResult.OutputS3KeyPrefix
		}
		runtimeStatus.StepName = pluginResult.StepName
	}

	if runtimeStatus.Status == ResultStatusFailed && runtimeStatus.Code == 0 {
		runtimeStatus.Code = 1
	}

	return runtimeStatus
}

// DocumentResultAggregator aggregates the result from the plugins to construct the agent response
func DocumentResultAggregator(log log.T,
	pluginID string,
	pluginOutputs map[string]*PluginResult) (documentStatus ResultStatus, runtimeStatusCounts map[string]int,
	runtimeStatusesFiltered map[string]*PluginRuntimeStatus, runtimeStatuses map[string]*PluginRuntimeStatus) {

	runtimeStatuses = make(map[string]*PluginRuntimeStatus)
	for pluginID, pluginResult := range pluginOutputs {
		rs := prepareRuntimeStatus(log, *pluginResult)
		runtimeStatuses[pluginID] = &rs
	}
	// TODO instance this needs to be revised to be in parity with ec2config
	documentStatus = ResultStatusSuccess
	runtimeStatusCounts = map[string]int{}
	pluginCounts := len(runtimeStatuses)

	for _, pluginResult := range runtimeStatuses {
		runtimeStatusCounts[string(pluginResult.Status)]++
	}
	if pluginID == "" {
		//	  New precedence order of plugin states
		//	  Failed > TimedOut > Cancelled > Success > Cancelling > InProgress > Pending
		//	  The above order is a contract between SSM service and agent and hence for the calculation of aggregate
		//	  status of a (command) document, we follow the above precedence order.
		//
		//	  Note:
		//	  A command could have been failed/cancelled even before a plugin started executing, during which pendingItems > 0
		//	  but overallResult.Status would be Failed/Cancelled. That's the reason we check for OverallResult status along
		//	  with number of failed/cancelled items.
		//    TODO : We need to handle above to be able to send document traceoutput in case of document level errors.

		// Skipped is a form of success
		successCounts := runtimeStatusCounts[string(ResultStatusSuccess)] + runtimeStatusCounts[string(ResultStatusSkipped)]

		if runtimeStatusCounts[string(ResultStatusSuccessAndReboot)] > 0 {
			documentStatus = ResultStatusSuccessAndReboot
		} else if runtimeStatusCounts[string(ResultStatusFailed)] > 0 {
			documentStatus = ResultStatusFailed
		} else if runtimeStatusCounts[string(ResultStatusTimedOut)] > 0 {
			documentStatus = ResultStatusTimedOut
		} else if runtimeStatusCounts[string(ResultStatusCancelled)] > 0 {
			documentStatus = ResultStatusCancelled
		} else if successCounts == pluginCounts {
			documentStatus = ResultStatusSuccess
		} else {
			documentStatus = ResultStatusInProgress
		}
	} else {
		documentStatus = ResultStatusInProgress
	}

	runtimeStatusesFiltered = make(map[string]*PluginRuntimeStatus)

	// When pluginID isn't empty, the agent is sending update information about the current plugin.
	// When it's empty, the agent is sending update information about the document.
	// When it's empty, the agent only sends plugin status if it's a single plugin invocation.
	if pluginID != "" {
		runtimeStatusesFiltered[pluginID] = runtimeStatuses[pluginID]
		runtimeStatuses = runtimeStatusesFiltered
	} else if len(runtimeStatuses) <= 1 {
		runtimeStatusesFiltered = runtimeStatuses
	}

	return documentStatus, runtimeStatusCounts, runtimeStatusesFiltered, runtimeStatuses

}
