// Copyright 2020 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 events

import (
	"errors"
	"fmt"
	"log"
	"strings"
	"time"

	cmdutil "github.com/aws/amazon-ec2-metadata-mock/pkg/cmd/cmdutil"
	cfg "github.com/aws/amazon-ec2-metadata-mock/pkg/config"
	e "github.com/aws/amazon-ec2-metadata-mock/pkg/error"
	se "github.com/aws/amazon-ec2-metadata-mock/pkg/mock/events"

	"github.com/spf13/cobra"
)

const (
	cfgPrefix = "events."

	// local flags
	eventCodeFlagName         = "code"
	eventStateFlagName        = "state"
	notBeforeFlagName         = "not-before"
	notAfterFlagName          = "not-after"
	notBeforeDeadlineFlagName = "not-before-deadline"

	// event codes
	instanceReboot     = "instance-reboot"
	systemReboot       = "system-reboot"
	systemMaintenance  = "system-maintenance"
	instanceRetirement = "instance-retirement"
	instanceStop       = "instance-stop"

	// event states
	active    = "active"
	completed = "completed"
	canceled  = "canceled"

	// default date diffs (in days) in metadata
	notAfterDiff          = 7
	notBeforeDeadlineDiff = 9
)

var (
	c cfg.Config

	// Command represents the CLI command
	Command *cobra.Command

	// constraints
	validEventCodes  = []string{instanceReboot, systemReboot, systemMaintenance, instanceRetirement, instanceStop}
	validEventStates = []string{active, completed, canceled}
	constraints      = []string{
		"event-code can be one of the following: " + strings.Join(validEventCodes, ","),
		"state can be one of the following: " + strings.Join(validEventStates, ","),
	}

	// defaults
	defaultCfg = map[string]interface{}{
		cfgPrefix + eventCodeFlagName:         systemReboot,
		cfgPrefix + eventStateFlagName:        active,
		cfgPrefix + notBeforeFlagName:         time.Now().Format(time.RFC3339),
		cfgPrefix + notAfterFlagName:          time.Now().Add(time.Hour * 24 * notAfterDiff).Format(time.RFC3339),
		cfgPrefix + notBeforeDeadlineFlagName: time.Now().Add(time.Hour * 24 * notBeforeDeadlineDiff).Format(time.RFC3339),
	}
)

func init() {
	cobra.OnInitialize(initConfig)
	Command = newCmd()
}

func initConfig() {
	cfg.LoadConfigFromDefaults(defaultCfg)
}

func newCmd() *cobra.Command {
	var cmd = &cobra.Command{
		Use:     "events [--code CODE] [--state STATE] [--not-after] [--not-before-deadline]",
		Aliases: []string{"se", "scheduledevents"},
		PreRunE: preRun,
		Example: fmt.Sprintf("  %s events -h \tevents help \n  %s events -o instance-stop --state active -d\t\tmocks an active and upcoming scheduled event for instance stop with a deadline for the event start time", cmdutil.BinName, cmdutil.BinName),
		Run:     run,
		Short:   "Mock EC2 maintenance events",
		Long:    "Mock EC2 maintenance events",
	}

	// local flags
	cmd.Flags().StringP(eventCodeFlagName, "o", "", "event code in the scheduled event (default: system-reboot)\nevent-code can be one of the following: "+strings.Join(validEventCodes, ","))
	cmd.Flags().StringP(eventStateFlagName, "t", "", "state of the scheduled event (default: active)\nstate can be one of the following: "+strings.Join(validEventStates, ","))
	cmd.Flags().StringP(notBeforeFlagName, "b", "", "the earliest start time for the scheduled event in RFC3339 format E.g. 2020-01-07T01:03:47Z (default: application start time in UTC)")
	cmd.Flags().StringP(notAfterFlagName, "a", "", "the latest end time for the scheduled event in RFC3339 format E.g. 2020-01-07T01:03:47Z default: application start time + 7 days in UTC))")
	cmd.Flags().StringP(notBeforeDeadlineFlagName, "l", "", "the deadline for starting the event in RFC3339 format E.g. 2020-01-07T01:03:47Z (default: application start time + 9 days in UTC)")

	// bind local flags to config
	cfg.BindFlagSetWithKeyPrefix(cmd.Flags(), cfgPrefix)
	return cmd
}

// SetConfig sets the local config
func SetConfig(config cfg.Config) {
	c = config
}

func preRun(cmd *cobra.Command, args []string) error {
	if cfgErrors := ValidateLocalConfig(); cfgErrors != nil {
		return errors.New(strings.Join(cfgErrors, ""))
	}
	return nil
}

// ValidateLocalConfig validates all local config and returns a slice of error messages
func ValidateLocalConfig() []string {
	var errStrings []string
	c := c.EventsConfig

	// validate event code
	if ok := cmdutil.Contains(validEventCodes, c.EventCode); !ok {
		errStrings = append(errStrings, e.FlagValidationError{
			FlagName:     eventCodeFlagName,
			Allowed:      strings.Join(validEventCodes, ","),
			InvalidValue: c.EventCode}.Error(),
		)
	}

	// validate event status
	if ok := cmdutil.Contains(validEventStates, c.EventState); !ok {
		errStrings = append(errStrings, e.FlagValidationError{
			FlagName:     eventStateFlagName,
			Allowed:      strings.Join(validEventStates, ","),
			InvalidValue: c.EventState}.Error(),
		)
	}

	// validate time flags
	if err := cmdutil.ValidateRFC3339TimeFormat(notBeforeFlagName, c.NotBefore); err != nil {
		errStrings = append(errStrings, err.Error())
	}
	if err := cmdutil.ValidateRFC3339TimeFormat(notAfterFlagName, c.NotAfter); err != nil {
		errStrings = append(errStrings, err.Error())
	}
	if err := cmdutil.ValidateRFC3339TimeFormat(notBeforeDeadlineFlagName, c.NotBeforeDeadline); err != nil {
		errStrings = append(errStrings, err.Error())
	}
	return errStrings
}

func run(cmd *cobra.Command, args []string) {
	log.Printf("Initiating %s for EC2 Events on port %s\n", cmdutil.BinName, c.Server.Port)
	cmdutil.PrintFlags(cmd.Flags())
	cmdutil.RegisterHandlers(cmd, c)
	se.Mock(c)
}
