// Copyright 2021 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 messageservice will be responsible for initializing MDS and MGS interactors and then
// launch message handlers to handle the commands received from interactors.
// This package is the starting point for the message service module.
package messageservice

import (
	"fmt"
	"testing"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/context"
	"github.com/aws/amazon-ssm-agent/agent/log"
	"github.com/aws/amazon-ssm-agent/agent/messageservice/interactor"
	interactorMock "github.com/aws/amazon-ssm-agent/agent/messageservice/interactor/mocks"
	"github.com/aws/amazon-ssm-agent/agent/messageservice/messagehandler"
	"github.com/aws/amazon-ssm-agent/agent/messageservice/messagehandler/mocks"
	"github.com/aws/amazon-ssm-agent/agent/messageservice/messagehandler/processorwrappers"
	processorWrapperMock "github.com/aws/amazon-ssm-agent/agent/messageservice/messagehandler/processorwrappers/mocks"
	"github.com/aws/amazon-ssm-agent/agent/messageservice/utils"
	contextmocks "github.com/aws/amazon-ssm-agent/agent/mocks/context"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
)

type MessageServiceTestSuite struct {
	suite.Suite
}

// Execute the test suite
func TestMessageServiceTestSuite(t *testing.T) {
	suite.Run(t, new(MessageServiceTestSuite))
}

// TestMessageService_Initialize tests initialize scenarios
func (suite *MessageServiceTestSuite) TestMessageService_Initialize() {
	// Nano platform check
	ctx := contextmocks.NewMockDefault()
	isPlatformNanoServer = func(log log.T) (bool, error) {
		return true, nil
	}
	msgService := NewService(ctx).(*MessageService)
	assert.Equal(suite.T(), 1, len(msgService.interactors), "length not expected for nano")

	// container mode check
	isPlatformNanoServer = func(log log.T) (bool, error) {
		return false, nil
	}
	ssmAgentConfig := appconfig.SsmagentConfig{
		Agent: appconfig.AgentInfo{
			ContainerMode: true,
		},
	}
	ctx = contextmocks.NewMockDefaultWithConfig(ssmAgentConfig)
	msgService = NewService(ctx).(*MessageService)
	assert.Equal(suite.T(), 1, len(msgService.interactors), "length not expected for container")

	// for other platforms
	isPlatformNanoServer = func(log log.T) (bool, error) {
		return false, nil
	}
	ssmAgentConfig = appconfig.SsmagentConfig{
		Agent: appconfig.AgentInfo{
			ContainerMode: false,
		},
	}
	ctx = contextmocks.NewMockDefaultWithConfig(ssmAgentConfig)
	msgService = NewService(ctx).(*MessageService)
	assert.Equal(suite.T(), 2, len(msgService.interactors), "length not expected for other platforms")

	// other remaining case
	isPlatformNanoServer = func(log log.T) (bool, error) {
		return true, nil
	}
	ssmAgentConfig = appconfig.SsmagentConfig{
		Agent: appconfig.AgentInfo{
			ContainerMode: true,
		},
	}
	ctx = contextmocks.NewMockDefaultWithConfig(ssmAgentConfig)
	msgService = NewService(ctx).(*MessageService)
	assert.Equal(suite.T(), 0, len(msgService.interactors), "length not expected for container")
}

// TestMessageService_ModuleName tests module name
func (suite *MessageServiceTestSuite) TestMessageService_ModuleName() {
	isPlatformNanoServer = func(log log.T) (bool, error) {
		return false, nil
	}
	ssmAgentConfig := appconfig.SsmagentConfig{
		Agent: appconfig.AgentInfo{
			ContainerMode: false,
		},
	}
	ctx := contextmocks.NewMockDefaultWithConfig(ssmAgentConfig)
	msgService := NewService(ctx)
	assert.Equal(suite.T(), msgService.ModuleName(), ServiceName, "invalid module name")
}

// TestMessageService_ModuleExecute tests module execute
func (suite *MessageServiceTestSuite) TestMessageService_ModuleExecute() {
	isPlatformNanoServer = func(log log.T) (bool, error) {
		return false, nil
	}
	ssmAgentConfig := appconfig.SsmagentConfig{
		Agent: appconfig.AgentInfo{
			ContainerMode: false,
		},
	}
	ctx := contextmocks.NewMockDefaultWithConfig(ssmAgentConfig)
	msgService := NewService(ctx).(*MessageService)
	assert.Equal(suite.T(), 2, len(msgService.interactors), "length not expected for other platforms")

	// clearing interactor array
	msgService.interactors = make([]interactor.IInteractor, 0)
	interactorMockObj := &interactorMock.IInteractor{}
	interactorMockObj.On("Initialize", mock.Anything).Return(nil)
	interactorMockObj.On("PostProcessorInitialization", mock.Anything).Return(nil)
	interactorMockObj.On("GetName").Return(mock.Anything)
	interactorMockObj.On("GetSupportedWorkers").Return([]utils.WorkerName{utils.DocumentWorkerName, utils.SessionWorkerName})
	msgService.interactors = append(msgService.interactors, interactorMockObj, interactorMockObj)
	messageHandlerMock := &mocks.IMessageHandler{}
	msgService.messageHandler = messageHandlerMock
	messageHandlerMock.On("Initialize").Return(nil)
	messageHandlerMock.On("Submit", mock.Anything).Return(messagehandler.ErrorCode(""))
	messageHandlerMock.On("RegisterProcessor", mock.Anything).Return(nil)

	processorWrapperMock1 := &processorWrapperMock.IProcessorWrapper{}
	processorWrapperMock1.On("GetName").Return(mock.Anything)
	processorWrapperMock1.On("Initialize", mock.Anything).Return(nil)

	processorWrapperMock2 := &processorWrapperMock.IProcessorWrapper{}
	processorWrapperMock2.On("GetName").Return(mock.Anything)
	processorWrapperMock2.On("Initialize", mock.Anything).Return(nil)

	// Missing Processor Wrapper case
	getProcessorWrapperDelegateMap = func() map[utils.WorkerName]func(context.T, *utils.ProcessorWorkerConfig) processorwrappers.IProcessorWrapper {
		return map[utils.WorkerName]func(context.T, *utils.ProcessorWorkerConfig) processorwrappers.IProcessorWrapper{}
	}
	msgService.ModuleExecute()
	messageHandlerMock.AssertNumberOfCalls(suite.T(), "Initialize", 1)
	interactorMockObj.AssertNumberOfCalls(suite.T(), "Initialize", 2)

	// Processor Wrapper available case
	getProcessorWrapperDelegateMap = func() map[utils.WorkerName]func(context.T, *utils.ProcessorWorkerConfig) processorwrappers.IProcessorWrapper {
		delegateMap := make(map[utils.WorkerName]func(context.T, *utils.ProcessorWorkerConfig) processorwrappers.IProcessorWrapper)
		delegateMap[utils.DocumentWorkerName] = func(context.T, *utils.ProcessorWorkerConfig) processorwrappers.IProcessorWrapper {
			return processorWrapperMock1
		}
		delegateMap[utils.SessionWorkerName] = func(context.T, *utils.ProcessorWorkerConfig) processorwrappers.IProcessorWrapper {
			return processorWrapperMock2
		}
		return delegateMap
	}
	msgService.ModuleExecute()
	messageHandlerMock.AssertNumberOfCalls(suite.T(), "Initialize", 2) // +1 from previous execution
	interactorMockObj.AssertNumberOfCalls(suite.T(), "Initialize", 4)
}

// TestMessageService_ModuleExecute tests module execute
func (suite *MessageServiceTestSuite) TestMessageService_ModuleStop() {
	isPlatformNanoServer = func(log log.T) (bool, error) {
		return false, nil
	}
	ssmAgentConfig := appconfig.SsmagentConfig{
		Agent: appconfig.AgentInfo{
			ContainerMode: false,
		},
	}
	ctx := contextmocks.NewMockDefaultWithConfig(ssmAgentConfig)
	msgService := NewService(ctx).(*MessageService)
	assert.Equal(suite.T(), 2, len(msgService.interactors), "length not expected for other platforms")

	// clearing interactor array
	msgService.interactors = make([]interactor.IInteractor, 0)
	interactorMockObj := &interactorMock.IInteractor{}
	interactorMockObj.On("PreProcessorClose")
	interactorMockObj.On("Close").Return(nil)
	interactorMockObj.On("GetName").Return("")
	msgService.interactors = append(msgService.interactors, interactorMockObj, interactorMockObj)
	messageHandlerMock := &mocks.IMessageHandler{}
	messageHandlerMock.On("Stop", mock.Anything).Return(nil)
	msgService.messageHandler = messageHandlerMock
	err := msgService.ModuleStop()
	assert.Nil(suite.T(), err, "stop should not return error")

	// Close error - expected stop error
	msgService.interactors = make([]interactor.IInteractor, 0)
	interactorMockObj = &interactorMock.IInteractor{}
	interactorMockObj.On("PreProcessorClose").Return(nil)
	interactorMockObj.On("Close").Return(fmt.Errorf("random error"))
	interactorMockObj.On("GetName").Return(mock.Anything)
	msgService.interactors = append(msgService.interactors, interactorMockObj, interactorMockObj)
	messageHandlerMock = &mocks.IMessageHandler{}
	messageHandlerMock.On("Stop", mock.Anything).Return(nil)
	msgService.messageHandler = messageHandlerMock
	err = msgService.ModuleStop()
	interactorMockObj.AssertNumberOfCalls(suite.T(), "Close", 2)
}
