// 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 processor manage polling of associations, dispatching association to processor
package processor

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"testing"
	"time"

	"github.com/aws/amazon-ssm-agent/agent/association/converter"
	"github.com/aws/amazon-ssm-agent/agent/association/model"
	"github.com/aws/amazon-ssm-agent/agent/contracts"
	"github.com/aws/amazon-ssm-agent/agent/mocks/context"
	messageContracts "github.com/aws/amazon-ssm-agent/agent/runcommand/contracts"
	"github.com/aws/aws-sdk-go/service/ssm"
	"github.com/stretchr/testify/assert"
)

const (
	FILE_VERSION_1_0 = "./testdata/sampleVersion1_0.json"
	FILE_VERSION_1_2 = "./testdata/sampleVersion1_2.json"
	FILE_VERSION_2_0 = "./testdata/sampleVersion2_0.json"
	FILE_PARAM_2_0   = "./testdata/sampleParams2_0StringMap.json"
)

func TestParseAssociationWithAssociationVersion1_2(t *testing.T) {
	processor := Processor{
		context: context.NewMockDefault(),
	}

	sampleFile := readFile(FILE_VERSION_1_2)

	instanceID := "i-test"
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	associationName := "testV1.2"
	documentVersion := "1"
	assocRawData := model.InstanceAssociation{
		CreateDate: time.Now(),
		Document:   &sampleFile,
	}
	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	assocRawData.Association.Name = &associationName
	assocRawData.Association.DocumentVersion = &documentVersion
	assocRawData.Association.AssociationId = &assocId
	assocRawData.Association.InstanceId = &instanceID

	params := make(map[string][]*string)
	address := "http://7-zip.org/a/7z1602-x64.msi"
	source := []*string{&address}
	params["source"] = source

	assocRawData.Association.Parameters = params

	docState, err := processor.parseAssociation(&assocRawData)

	documentInfo := new(contracts.DocumentInfo)
	documentInfo.AssociationID = assocId
	documentInfo.InstanceID = instanceID
	documentInfo.MessageID = fmt.Sprintf("aws.ssm.%v.%v", assocId, instanceID)
	documentInfo.DocumentName = associationName
	documentInfo.DocumentVersion = documentVersion

	pluginName := "aws:applications"
	pluginsInfo := make(map[string]contracts.PluginState)
	config := contracts.Configuration{}
	var plugin contracts.PluginState
	plugin.Configuration = config
	plugin.Id = pluginName
	pluginsInfo[pluginName] = plugin

	expectedDocState := contracts.DocumentState{
		InstancePluginsInformation: converter.ConvertPluginsInformation(pluginsInfo),
		DocumentType:               contracts.Association,
		SchemaVersion:              "1.2",
	}

	payload := &messageContracts.SendCommandPayload{}

	err2 := json.Unmarshal([]byte(*assocRawData.Document), &payload.DocumentContent)
	pluginConfig := payload.DocumentContent.RuntimeConfig[pluginName]

	assert.Equal(t, nil, err)
	assert.Equal(t, nil, err2)
	assert.Equal(t, expectedDocState.SchemaVersion, docState.SchemaVersion)
	assert.Equal(t, contracts.Association, docState.DocumentType)

	pluginInfo := docState.InstancePluginsInformation[0]
	expectedProp := []interface{}{map[string]interface{}{"source": *source[0], "sourceHash": "", "id": "0.aws:applications", "action": "Install", "parameters": ""}}

	assert.Equal(t, expectedProp, pluginInfo.Configuration.Properties)
	assert.Equal(t, pluginConfig.Settings, pluginInfo.Configuration.Settings)
	assert.Equal(t, documentInfo.MessageID, pluginInfo.Configuration.MessageId)
}

func TestParseAssociationWithAssociationVersion2_0(t *testing.T) {
	processor := Processor{
		context: context.NewMockDefault(),
	}

	sampleFile := readFile(FILE_VERSION_2_0)

	instanceID := "i-test"
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	associationName := "testV2.0"
	documentVersion := "1"
	assocRawData := model.InstanceAssociation{
		CreateDate: time.Now(),
		Document:   &sampleFile,
	}
	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	assocRawData.Association.Name = &associationName
	assocRawData.Association.DocumentVersion = &documentVersion
	assocRawData.Association.AssociationId = &assocId
	assocRawData.Association.InstanceId = &instanceID

	params := make(map[string][]*string)
	cmd0 := "ls"
	source0 := []*string{&cmd0}
	cmd1 := "pwd"
	source1 := []*string{&cmd1}
	params["runCommand0"] = source0
	params["runCommand1"] = source1

	assocRawData.Association.Parameters = params

	// test the method
	docState, err := processor.parseAssociation(&assocRawData)

	documentInfo := new(contracts.DocumentInfo)
	documentInfo.AssociationID = assocId
	documentInfo.InstanceID = instanceID
	documentInfo.MessageID = fmt.Sprintf("aws.ssm.%v.%v", assocId, instanceID)
	documentInfo.DocumentName = associationName
	documentInfo.DocumentVersion = documentVersion

	instancePluginsInfo := make([]contracts.PluginState, 2)

	action0 := "aws:runPowerShellScript"
	name0 := "runPowerShellScript1"
	var plugin0 contracts.PluginState
	plugin0.Configuration = contracts.Configuration{}
	plugin0.Id = name0
	plugin0.Name = action0
	instancePluginsInfo[0] = plugin0

	action1 := "aws:runPowerShellScript"
	name1 := "runPowerShellScript2"
	var plugin1 contracts.PluginState
	plugin1.Configuration = contracts.Configuration{}
	plugin1.Id = name1
	plugin1.Name = action1
	instancePluginsInfo[1] = plugin1

	expectedDocState := contracts.DocumentState{
		//DocumentInformation: documentInfo,
		InstancePluginsInformation: instancePluginsInfo,
		DocumentType:               contracts.Association,
		SchemaVersion:              "2.0",
	}

	assert.Equal(t, nil, err)
	assert.Equal(t, expectedDocState.SchemaVersion, docState.SchemaVersion)
	assert.Equal(t, contracts.Association, docState.DocumentType)
	assert.Equal(t, documentInfo.MessageID, docState.DocumentInformation.MessageID)

	pluginInfo1 := docState.InstancePluginsInformation[0]
	pluginInfo2 := docState.InstancePluginsInformation[1]

	assert.Equal(t, name0, pluginInfo1.Id)
	assert.Equal(t, name1, pluginInfo2.Id)
	assert.Equal(t, action0, pluginInfo1.Name)
	assert.Equal(t, action1, pluginInfo2.Name)

	expectProp1 := map[string]interface{}{"id": "0.aws:psModule", "runCommand": *source0[0]}
	expectProp2 := map[string]interface{}{"id": "1.aws:psModule", "runCommand": *source1[0]}

	assert.Equal(t, expectProp1, pluginInfo1.Configuration.Properties)
	assert.Equal(t, expectProp2, pluginInfo2.Configuration.Properties)
}

func TestParseAssociationWithAssociationVersion2_0_StringMapParams(t *testing.T) {
	processor := Processor{
		context: context.NewMockDefault(),
	}

	sampleFile := readFile(FILE_PARAM_2_0)

	instanceID := "i-test"
	assocId := "b2f71a28-cbe1-4429-b848-26c7e1f5ad0d"
	associationName := "testV2.0"
	documentVersion := "1"
	assocRawData := model.InstanceAssociation{
		CreateDate: time.Now(),
		Document:   &sampleFile,
	}
	assocRawData.Association = &ssm.InstanceAssociationSummary{}
	assocRawData.Association.Name = &associationName
	assocRawData.Association.DocumentVersion = &documentVersion
	assocRawData.Association.AssociationId = &assocId
	assocRawData.Association.InstanceId = &instanceID

	params := make(map[string][]*string)
	cmd0 := "{\"name\":\"AWS-RunPowerShellScript\"}"
	source0 := []*string{&cmd0}
	cmd1 := "pwd"
	source1 := []*string{&cmd1}
	params["sourceInfo"] = source0
	params["runCommand1"] = source1

	assocRawData.Association.Parameters = params

	// test the method
	docState, err := processor.parseAssociation(&assocRawData)

	documentInfo := new(contracts.DocumentInfo)
	documentInfo.AssociationID = assocId
	documentInfo.InstanceID = instanceID
	documentInfo.MessageID = fmt.Sprintf("aws.ssm.%v.%v", assocId, instanceID)
	documentInfo.DocumentName = associationName
	documentInfo.DocumentVersion = documentVersion

	instancePluginsInfo := make([]contracts.PluginState, 2)

	action0 := "aws:downloadContent"
	name0 := "downloadContent"
	var plugin0 contracts.PluginState
	plugin0.Configuration = contracts.Configuration{}
	plugin0.Id = name0
	plugin0.Name = action0
	instancePluginsInfo[0] = plugin0

	action1 := "aws:runPowerShellScript"
	name1 := "runPowerShellScript2"
	var plugin1 contracts.PluginState
	plugin1.Configuration = contracts.Configuration{}
	plugin1.Id = name1
	plugin1.Name = action1
	instancePluginsInfo[1] = plugin1

	expectedDocState := contracts.DocumentState{
		//DocumentInformation: documentInfo,
		InstancePluginsInformation: instancePluginsInfo,
		DocumentType:               contracts.Association,
		SchemaVersion:              "2.0",
	}

	assert.Equal(t, nil, err)
	assert.Equal(t, expectedDocState.SchemaVersion, docState.SchemaVersion)
	assert.Equal(t, contracts.Association, docState.DocumentType)
	assert.Equal(t, documentInfo.MessageID, docState.DocumentInformation.MessageID)

	pluginInfo1 := docState.InstancePluginsInformation[0]
	pluginInfo2 := docState.InstancePluginsInformation[1]

	assert.Equal(t, name0, pluginInfo1.Id)
	assert.Equal(t, name1, pluginInfo2.Id)
	assert.Equal(t, action0, pluginInfo1.Name)
	assert.Equal(t, action1, pluginInfo2.Name)

	expectProp1 := map[string]interface{}{"sourceType": "SSMDocument", "sourceInfo": *source0[0], "id": "0.aws.downloadContent"}
	expectProp2 := map[string]interface{}{"id": "1.aws:psModule", "runCommand": *source1[0]}

	assert.Equal(t, expectProp1, pluginInfo1.Configuration.Properties)
	assert.Equal(t, expectProp2, pluginInfo2.Configuration.Properties)
}

func readFile(fileName string) string {
	file, e := ioutil.ReadFile(fileName)
	if e != nil {
		fmt.Printf("File error: %v\n", e)
		os.Exit(1)
	}
	return string(file)
}
