// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT

package ssm

import (
	"fmt"
	"os"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ssm"

	"github.com/aws/amazon-cloudwatch-agent/tool/data"
	"github.com/aws/amazon-cloudwatch-agent/tool/processors"
	"github.com/aws/amazon-cloudwatch-agent/tool/runtime"
	"github.com/aws/amazon-cloudwatch-agent/tool/util"
)

const (
	ErrCodeAccessDeniedException = "AccessDeniedException"
	defaultRetryCount            = 1 //total attempts are defaultRetryCount+1
)

var Processor processors.Processor = &processor{}

type processor struct{}

func (p *processor) Process(ctx *runtime.Context, config *data.Config) {
	answer := util.Yes("Do you want to store the config in the SSM parameter store?")
	if !answer {
		return
	}

	serializedConfig := util.ReadConfigFromJsonFile()
	parameterStoreName := determineParameterStoreName(ctx)
	region := determineRegion(ctx)

	var err error
	for i := 0; i <= defaultRetryCount; i++ {
		creds := determineCreds(ctx)
		err = sendConfigToParameterStore(serializedConfig, parameterStoreName, region, creds)
		if err == nil {
			fmt.Printf("Successfully put config to parameter store %s.\n", parameterStoreName)
			return
		}

		if awsErr, ok := err.(awserr.Error); ok {
			switch awsErr.Code() {
			case ErrCodeAccessDeniedException:
				fmt.Printf("Please make sure the creds you used have the right permissions configured for SSM access.\n")
				continue
			default:
				fmt.Printf("Error code: %s, message: %s, original error: %v\n", awsErr.Code(), awsErr.Message(), awsErr.OrigErr())
			}
		}
		break
	}
	fmt.Printf("Error in putting config to parameter store %s: %v\n", parameterStoreName, err)
}

func (p *processor) NextProcessor(ctx *runtime.Context, config *data.Config) interface{} {
	return nil
}

func determineCreds(ctx *runtime.Context) *credentials.Credentials {
	accessKeys := []string{}
	sdkAccessKey, _, sdkCreds := util.SDKCredentials()
	sdkAccessKeyDesc := ""
	if sdkCreds != nil {
		sdkAccessKeyDesc = sdkAccessKey + "(From SDK)"
		accessKeys = append(accessKeys, sdkAccessKeyDesc)
	}

	fileAccessKey := ""
	fileAccessKeyDesc := ""
	fileCredentialsProvider := credentials.NewSharedCredentials("", "AmazonCloudWatchAgent")
	fileCreds, err := fileCredentialsProvider.Get()
	if err == nil {
		fileAccessKey = fileCreds.AccessKeyID
		fileAccessKeyDesc = fileAccessKey + "(From Profile: AmazonCloudWatchAgent)"
		accessKeys = append(accessKeys, fileAccessKeyDesc)
	}

	if len(accessKeys) > 0 {
		accessKeys = append(accessKeys, "Other")

		answer := util.Choice("Which AWS credential should be used to send json config to parameter store?", 1, accessKeys)
		if answer == sdkAccessKeyDesc {
			return sdkCreds
		} else if answer == fileAccessKeyDesc {
			return fileCredentialsProvider
		}
	}
	return askCreds()
}

func askCreds() *credentials.Credentials {
	accessKey := util.Ask("Please provide credentials to upload the json config file to parameter store.\nAWS Access Key:")
	secretKey := util.Ask("AWS Secret Key:")
	creds := credentials.NewStaticCredentials(accessKey, secretKey, "")
	return creds
}

func determineParameterStoreName(ctx *runtime.Context) string {
	defaultParameterStoreName := "AmazonCloudWatch-" + ctx.OsParameter
	parameterStoreName := util.AskWithDefault("What parameter store name do you want to use to store your config? (Use 'AmazonCloudWatch-' prefix if you use our managed AWS policy)", defaultParameterStoreName)
	return parameterStoreName
}

func determineRegion(ctx *runtime.Context) string {
	var region string
	if !ctx.IsOnPrem {
		region = util.DefaultEC2Region()
	} else {
		region = util.SDKRegionWithProfile("AmazonCloudWatchAgent")
	}
	if region == "" {
		region = "us-east-1"
	}
	region = util.AskWithDefault("Which region do you want to store the config in the parameter store?", region)
	return region
}

func sendConfigToParameterStore(config, parameterStoreName, region string, creds *credentials.Credentials) error {
	awsConfig := aws.NewConfig().WithRegion(region)
	if creds != nil {
		awsConfig = awsConfig.WithCredentials(creds)
	}
	ses, err := session.NewSession(awsConfig)
	if err != nil {
		fmt.Printf("Error in creating session:\n %v\n", err)
		return err
	}
	ssmClient := ssm.New(ses)
	input := ssm.PutParameterInput{}
	input.SetName(parameterStoreName)
	input.SetOverwrite(true)
	hostName, _ := os.Hostname()
	input.SetDescription(fmt.Sprintf("Generated by wizard on %s at %s", hostName, time.Now().Format(time.RFC1123)))
	input.SetType("String")
	input.SetValue(config)
	_, err = ssmClient.PutParameter(&input)
	return err
}
