// 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 frequentcollector_test provides the unit test code for the FrequentCollector
package frequentcollector_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/association/frequentcollector"
	"github.com/aws/amazon-ssm-agent/agent/association/rateexpr"
	"github.com/aws/amazon-ssm-agent/agent/contracts"
	"github.com/aws/amazon-ssm-agent/agent/mocks/context"
	"github.com/aws/aws-sdk-go/service/ssm"
	"github.com/stretchr/testify/assert"

	AssociationModel "github.com/aws/amazon-ssm-agent/agent/association/model"
)

func TestIsFrequentCollectorEnabled_True(t *testing.T) {
	properties := make(map[string]interface{})
	properties["applications"] = "Enabled"
	properties["changeDetectionFrequency"] = "2"

	strTypes := []string{"AWS:Application", "AWS:AWSComponent"}
	types := make([]interface{}, len(strTypes))
	for i, s := range strTypes {
		types[i] = s
	}
	properties["changeDetectionTypes"] = types

	docState := buildInventoryDocumentState(properties)
	assocRawData := buildRatedInstanceAssociation(30)
	contextMock := context.NewMockDefault()
	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsFrequentCollectorEnabled(contextMock, &docState, &assocRawData)

	assert.Equal(t, result, true, "frequent collector enabled")
}

func TestIsFrequentCollectorEnabled_False_AbsenceOfDetectionTypes(t *testing.T) {
	properties := make(map[string]interface{})
	properties["applications"] = "Enabled"
	properties["changeDetectionFrequency"] = "2"

	docState := buildInventoryDocumentState(properties)
	assocRawData := buildRatedInstanceAssociation(30)
	contextMock := context.NewMockDefault()

	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsFrequentCollectorEnabled(contextMock, &docState, &assocRawData)

	assert.Equal(t, result, false, "frequent collector is not enabled")
}

func TestIsFrequentCollectorEnabled_False_AbsenceOfDetectionFrequency(t *testing.T) {
	properties := make(map[string]interface{})
	properties["applications"] = "Enabled"

	strTypes := []string{"AWS:Application", "AWS:AWSComponent"}
	types := make([]interface{}, len(strTypes))
	for i, s := range strTypes {
		types[i] = s
	}
	properties["changeDetectionTypes"] = types

	docState := buildInventoryDocumentState(properties)
	assocRawData := buildRatedInstanceAssociation(30)
	contextMock := context.NewMockDefault()

	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsFrequentCollectorEnabled(contextMock, &docState, &assocRawData)

	assert.Equal(t, result, false, "frequent collector is not enabled")
}

func TestIsFrequentCollectorEnabled_False_ZeroDetectionFrequency(t *testing.T) {
	properties := make(map[string]interface{})
	properties["applications"] = "Enabled"
	properties["changeDetectionFrequency"] = "0"

	strTypes := []string{"AWS:Application", "AWS:AWSComponent"}
	types := make([]interface{}, len(strTypes))
	for i, s := range strTypes {
		types[i] = s
	}
	properties["changeDetectionTypes"] = types

	docState := buildInventoryDocumentState(properties)
	assocRawData := buildRatedInstanceAssociation(30)
	contextMock := context.NewMockDefault()

	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsFrequentCollectorEnabled(contextMock, &docState, &assocRawData)

	assert.Equal(t, result, false, "frequent collector is not enabled")
}

func TestIsFrequentCollectorEnabled_False_NegativeDetectionFrequency(t *testing.T) {
	properties := make(map[string]interface{})
	properties["applications"] = "Enabled"
	properties["changeDetectionFrequency"] = "-3"

	strTypes := []string{"AWS:Application", "AWS:AWSComponent"}
	types := make([]interface{}, len(strTypes))
	for i, s := range strTypes {
		types[i] = s
	}
	properties["changeDetectionTypes"] = types

	docState := buildInventoryDocumentState(properties)
	assocRawData := buildRatedInstanceAssociation(30)
	contextMock := context.NewMockDefault()

	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsFrequentCollectorEnabled(contextMock, &docState, &assocRawData)

	assert.Equal(t, result, false, "frequent collector is not enabled")
}

func TestIsFrequentCollectorEnabled_False_NonIntegerInputForDetectionFrequency(t *testing.T) {
	properties := make(map[string]interface{})
	properties["applications"] = "Enabled"
	properties["changeDetectionFrequency"] = "3.56"

	strTypes := []string{"AWS:Application", "AWS:AWSComponent"}
	types := make([]interface{}, len(strTypes))
	for i, s := range strTypes {
		types[i] = s
	}
	properties["changeDetectionTypes"] = types

	docState := buildInventoryDocumentState(properties)
	assocRawData := buildRatedInstanceAssociation(30)
	contextMock := context.NewMockDefault()

	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsFrequentCollectorEnabled(contextMock, &docState, &assocRawData)

	assert.Equal(t, result, false, "frequent collector is not enabled")
}

func TestIsFrequentCollectorEnabled_False_RandomStringInputForDetectionFrequency(t *testing.T) {
	properties := make(map[string]interface{})
	properties["applications"] = "Enabled"
	properties["changeDetectionFrequency"] = "hello,world"

	strTypes := []string{"AWS:Application", "AWS:AWSComponent"}
	types := make([]interface{}, len(strTypes))
	for i, s := range strTypes {
		types[i] = s
	}
	properties["changeDetectionTypes"] = types

	docState := buildInventoryDocumentState(properties)
	assocRawData := buildRatedInstanceAssociation(30)
	contextMock := context.NewMockDefault()

	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsFrequentCollectorEnabled(contextMock, &docState, &assocRawData)

	assert.Equal(t, result, false, "frequent collector is not enabled")
}

func TestIsFrequentCollectorEnabled_False_ChangeDetectionTypeDisabled(t *testing.T) {
	properties := make(map[string]interface{})
	properties["applications"] = "Disabled"
	properties["changeDetectionFrequency"] = "2"

	strTypes := []string{"AWS:Application"}
	types := make([]interface{}, len(strTypes))
	for i, s := range strTypes {
		types[i] = s
	}
	properties["changeDetectionTypes"] = types

	docState := buildInventoryDocumentState(properties)
	assocRawData := buildRatedInstanceAssociation(30)
	contextMock := context.NewMockDefault()

	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsFrequentCollectorEnabled(contextMock, &docState, &assocRawData)

	assert.Equal(t, result, false, "frequent collector is not enabled")
}

func TestGetIntervalInSeconds_InvalidFrequency(t *testing.T) {
	assocRawData := buildRatedInstanceAssociation(30)
	frequentCollector := frequentcollector.GetFrequentCollector()
	valid, _ := frequentCollector.GetIntervalInSeconds(0, assocRawData.ParsedExpression)

	assert.Equal(t, valid, false, "the frequency is invalid")
}

func TestGetIntervalInSeconds_NormalFrequency(t *testing.T) {
	assocRawData := buildRatedInstanceAssociation(30)
	frequentCollector := frequentcollector.GetFrequentCollector()
	valid, seconds := frequentCollector.GetIntervalInSeconds(3, assocRawData.ParsedExpression)

	assert.Equal(t, valid, true, "the frequency is valid")
	assert.Equal(t, seconds, 600, "the interval is correct")
}

func TestGetIntervalInSeconds_TooHighFrequency(t *testing.T) {
	assocRawData := buildRatedInstanceAssociation(30)
	frequentCollector := frequentcollector.GetFrequentCollector()
	valid, seconds := frequentCollector.GetIntervalInSeconds(60, assocRawData.ParsedExpression)

	assert.Equal(t, valid, true, "the frequency is valid")
	assert.Equal(t, seconds, 300, "the interval is correct")
}

func TestIsSoftwareInventoryAssociation_True(t *testing.T) {
	pluginState := contracts.PluginState{
		Name: appconfig.PluginNameAwsSoftwareInventory,
	}

	docState := contracts.DocumentState{
		InstancePluginsInformation: []contracts.PluginState{pluginState},
	}

	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsSoftwareInventoryAssociation(&docState)
	assert.Equal(t, result, true, "This is a software inventory association")
}

func TestIsSoftwareInventoryAssociation_False(t *testing.T) {
	pluginState := contracts.PluginState{
		Name: appconfig.PluginNameAwsRunShellScript,
	}

	docState := contracts.DocumentState{
		InstancePluginsInformation: []contracts.PluginState{pluginState},
	}

	frequentCollector := frequentcollector.GetFrequentCollector()
	result := frequentCollector.IsSoftwareInventoryAssociation(&docState)
	assert.Equal(t, result, false, "This is NOT a software inventory association")
}

func buildRatedInstanceAssociation(rateInMinutes int) AssociationModel.InstanceAssociation {
	assocRawData := AssociationModel.InstanceAssociation{
		CreateDate: time.Now(),
	}

	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	testRateExpression := fmt.Sprintf("rate(%d minutes)", rateInMinutes)
	assocRawData.Association.ScheduleExpression = &testRateExpression
	assocRawData.ParsedExpression, _ = rateexpr.Parse(testRateExpression)
	return assocRawData
}

func buildInventoryDocumentState(properties map[string]interface{}) contracts.DocumentState {
	var conf contracts.Configuration
	conf.Properties = properties

	pluginState := contracts.PluginState{
		Name: appconfig.PluginNameAwsSoftwareInventory,
	}
	pluginState.Configuration = conf

	docState := contracts.DocumentState{
		InstancePluginsInformation: []contracts.PluginState{pluginState},
	}
	return docState
}
