// Copyright 2017 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 model

import (
	"testing"
	"time"

	"github.com/aws/amazon-ssm-agent/agent/association/scheduleexpression"
	"github.com/aws/amazon-ssm-agent/agent/log/logger"
	"github.com/aws/aws-sdk-go/service/ssm"
	"github.com/stretchr/testify/assert"
)

func TestRateExpressionIsParsedSuccessfullyWhenItIsValid(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{
		CreateDate: time.Now(),
	}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testRateExpression := "rate(5 days)"
	assocRawData.Association.ScheduleExpression = &testRateExpression

	// Act
	err := assocRawData.ParseExpression(logger)

	// Assert
	assert.Nil(t, err)
	assert.NotNil(t, assocRawData.ParsedExpression)
}

func TestUpperCasedRateExpressionIsParsedSuccessfullyWhenItIsValid(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{
		CreateDate: time.Now(),
	}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testRateExpression := "RATE(5 DAYS)"
	assocRawData.Association.ScheduleExpression = &testRateExpression

	// Act
	err := assocRawData.ParseExpression(logger)

	// Assert
	assert.Nil(t, err)
	assert.NotNil(t, assocRawData.ParsedExpression)
}

func TestCronExpressionIsParsedSuccessfullyWhenItIsValid(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{
		CreateDate: time.Now(),
	}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testExpression := "0 0 0/1 * * ? *"
	testCronExpression := "cron(" + testExpression + ")"
	assocRawData.Association.ScheduleExpression = &testCronExpression

	// Act
	err := assocRawData.ParseExpression(logger)

	// Assert
	assert.Equal(t, nil, err)
	assert.NotNil(t, assocRawData.ParsedExpression)
}

func TestParseExpressionReturnsErrorWhenCronExpressionIsInvalid(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{
		CreateDate: time.Now(),
	}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testExpression := "test 0 0 0/1 * * ? *"
	testCronExpression := "cron(" + testExpression + ")"
	assocRawData.Association.ScheduleExpression = &testCronExpression

	// Act
	err := assocRawData.ParseExpression(logger)

	// Assert
	assert.NotNil(t, err)
}

func TestNextScheduledDateIsCorrectWhenParsedExpressionIsValidCronExpression(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testAssociationName := "Test"
	assocRawData.Association.Name = &testAssociationName
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	assocRawData.Association.AssociationId = &assocId
	testExpression := "0 0 0/1 * * ? *" // hourly cron expression
	testCronExpression := "cron(" + testExpression + ")"
	assocRawData.Association.ScheduleExpression = &testCronExpression
	parsedScheduleExpression, _ := scheduleexpression.CreateScheduleExpression(logger, testCronExpression)

	assocRawData.ParsedExpression = parsedScheduleExpression
	assocRawData.Association.ScheduleExpression = &testCronExpression

	// time.Date takes following parameters:
	// Year, Month, Day, Hour, Minute, Second, Nanosecond, Location
	lastExecutionDateTime := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	assocRawData.Association.LastExecutionDate = &lastExecutionDateTime

	expectedNextScheduledDateTime := time.Date(
		2009, 11, 17, 21, 00, 00, 000000000, time.UTC)
	// Act
	assocRawData.SetNextScheduledDate(logger)

	// Assert
	assert.Equal(t, expectedNextScheduledDateTime, *assocRawData.NextScheduledDate)
}

func TestNextScheduledDateIsCorrectWhenParsedExpressionIsValidUpperCasedCronExpression(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testAssociationName := "Test"
	assocRawData.Association.Name = &testAssociationName
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	assocRawData.Association.AssociationId = &assocId
	testExpression := "0 0 0/1 * * ? *" // hourly cron expression
	testCronExpression := "CRON(" + testExpression + ")"
	assocRawData.Association.ScheduleExpression = &testCronExpression
	parsedScheduleExpression, _ := scheduleexpression.CreateScheduleExpression(logger, testCronExpression)

	assocRawData.ParsedExpression = parsedScheduleExpression
	assocRawData.Association.ScheduleExpression = &testCronExpression

	// time.Date takes following parameters:
	// Year, Month, Day, Hour, Minute, Second, Nanosecond, Location
	lastExecutionDateTime := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	assocRawData.Association.LastExecutionDate = &lastExecutionDateTime

	expectedNextScheduledDateTime := time.Date(
		2009, 11, 17, 21, 00, 00, 000000000, time.UTC)
	// Act
	assocRawData.SetNextScheduledDate(logger)

	// Assert
	assert.Equal(t, expectedNextScheduledDateTime, *assocRawData.NextScheduledDate)
}

func TestNextScheduledDateIsCorrectWhenExpressionIsValidCronAndHasNotBeenParsedBefore(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testAssociationName := "Test"
	assocRawData.Association.Name = &testAssociationName
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	assocRawData.Association.AssociationId = &assocId
	testExpression := "0 0 0/1 * * ? *" // hourly cron expression
	testCronExpression := "cron(" + testExpression + ")"
	assocRawData.Association.ScheduleExpression = &testCronExpression

	// time.Date takes following parameters:
	// Year, Month, Day, Hour, Minute, Second, Nanosecond, Location
	lastExecutionDateTime := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	assocRawData.Association.LastExecutionDate = &lastExecutionDateTime

	expectedNextScheduledDateTime := time.Date(
		2009, 11, 17, 21, 00, 00, 000000000, time.UTC)

	// Act
	assocRawData.SetNextScheduledDate(logger)

	// Assert
	assert.Equal(t, expectedNextScheduledDateTime, *assocRawData.NextScheduledDate)
}

func TestNextScheduledDateIsNilWhenCronExpressionIsInvalid(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testAssociationName := "Test"
	assocRawData.Association.Name = &testAssociationName
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	assocRawData.Association.AssociationId = &assocId
	testExpression := "Foo0 0 0/1 * * ? *" // hourly cron expression
	testCronExpression := "cron(" + testExpression + ")"
	assocRawData.Association.ScheduleExpression = &testCronExpression

	// time.Date takes following parameters:
	// Year, Month, Day, Hour, Minute, Second, Nanosecond, Location
	lastExecutionDateTime := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	assocRawData.Association.LastExecutionDate = &lastExecutionDateTime

	// Act
	assocRawData.SetNextScheduledDate(logger)

	// Assert
	assert.Nil(t, assocRawData.NextScheduledDate)
}

func TestNextScheduledDateIsCorrectWhenParsedExpressionIsValidRateExpression(t *testing.T) {
	// Assemble
	logger := logger.DefaultLogger()

	testInstanceAssociation := InstanceAssociation{}

	testInstanceAssociation.Association = &ssm.InstanceAssociationSummary{}
	testAssociationName := "Test"
	testInstanceAssociation.Association.Name = &testAssociationName
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	testInstanceAssociation.Association.AssociationId = &assocId
	testRateExpression := "rate(3 days)"
	testInstanceAssociation.Association.ScheduleExpression = &testRateExpression
	parsedScheduleExpression, _ := scheduleexpression.CreateScheduleExpression(logger, testRateExpression)
	testInstanceAssociation.ParsedExpression = parsedScheduleExpression

	// time.Date takes following parameters:
	// Year, Month, Day, Hour, Minute, Second, Nanosecond, Location
	lastExecutionDateTime := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	testInstanceAssociation.Association.LastExecutionDate = &lastExecutionDateTime

	expectedNextScheduledDateTime := time.Date(
		2009, 11, 20, 20, 34, 58, 651387237, time.UTC)
	// Act
	testInstanceAssociation.SetNextScheduledDate(logger)

	// Assert
	assert.Equal(t, expectedNextScheduledDateTime, *testInstanceAssociation.NextScheduledDate)
}

func TestNextScheduledDateIsCorrectWhenExpressionIsValidRateAndHasNoteBeenParsedBefore(t *testing.T) {
	// Assemble
	logger := logger.DefaultLogger()

	testInstanceAssociation := InstanceAssociation{}

	testInstanceAssociation.Association = &ssm.InstanceAssociationSummary{}
	testAssociationName := "Test"
	testInstanceAssociation.Association.Name = &testAssociationName
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	testInstanceAssociation.Association.AssociationId = &assocId
	testRateExpression := "rate(3 days)"
	testInstanceAssociation.Association.ScheduleExpression = &testRateExpression

	// time.Date takes following parameters:
	// Year, Month, Day, Hour, Minute, Second, Nanosecond, Location
	lastExecutionDateTime := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	testInstanceAssociation.Association.LastExecutionDate = &lastExecutionDateTime

	expectedNextScheduledDateTime := time.Date(
		2009, 11, 20, 20, 34, 58, 651387237, time.UTC)
	// Act
	testInstanceAssociation.SetNextScheduledDate(logger)

	// Assert
	assert.Equal(t, expectedNextScheduledDateTime, *testInstanceAssociation.NextScheduledDate)
}

func TestNextScheduleDateIsNilWhenRateExpressionIsInvalid(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testAssociationName := "Test"
	assocRawData.Association.Name = &testAssociationName
	testRateExpression := "rate(test1 day2)"
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	assocRawData.Association.AssociationId = &assocId
	assocRawData.Association.ScheduleExpression = &testRateExpression

	// time.Date takes following parameters:
	// Year, Month, Day, Hour, Minute, Second, Nanosecond, Location
	lastExecutionDateTime := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	assocRawData.Association.LastExecutionDate = &lastExecutionDateTime

	// Act
	assocRawData.SetNextScheduledDate(logger)

	// Assert
	assert.Nil(t, assocRawData.NextScheduledDate)
}

func TestNextScheduleDateIsNilWhenExpressionTypeIsUnknownAndAssociationHasPreviouslyBeenExecuted(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testAssociationName := "Test"
	assocRawData.Association.Name = &testAssociationName
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	assocRawData.Association.AssociationId = &assocId
	testRateExpression := "at(5 PM)"
	assocRawData.Association.ScheduleExpression = &testRateExpression

	// Setting last execution date implies that association has been executed already.
	// time.Date takes following parameters:
	// Year, Month, Day, Hour, Minute, Second, Nanosecond, Location
	lastExecutionDateTime := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	assocRawData.Association.LastExecutionDate = &lastExecutionDateTime

	// Act
	assocRawData.SetNextScheduledDate(logger)

	// Assert
	assert.Nil(t, assocRawData.NextScheduledDate)
}

func TestNextScheduledDateIsNilWhenAssociationExpressionTypeIsUnknown(t *testing.T) {

	// Assemble
	logger := logger.DefaultLogger()

	assocRawData := InstanceAssociation{}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testAssociationName := "Test"
	assocRawData.Association.Name = &testAssociationName
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	assocRawData.Association.AssociationId = &assocId
	testRateExpression := "at(5 PM)"
	assocRawData.Association.ScheduleExpression = &testRateExpression

	// Setting last execution date implies that association has been executed already.
	// time.Date takes following parameters:
	// Year, Month, Day, Hour, Minute, Second, Nanosecond, Location
	lastExecutionDateTime := time.Date(
		2009, 11, 17, 20, 34, 58, 651387237, time.UTC)
	assocRawData.Association.LastExecutionDate = &lastExecutionDateTime

	// Act
	assocRawData.SetNextScheduledDate(logger)

	// Assert
	assert.Nil(t, assocRawData.NextScheduledDate)
}
