// 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 longrunningprovider provides an interface to start/stop a worker process for long-running tasks.
package longrunningprovider

import (
	"fmt"
	"testing"
	"time"

	logmocks "github.com/aws/amazon-ssm-agent/agent/mocks/log"
	reboot "github.com/aws/amazon-ssm-agent/core/app/reboot/model"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/log"
	"github.com/aws/amazon-ssm-agent/common/message"
	contextmocks "github.com/aws/amazon-ssm-agent/core/app/context/mocks"
	messagebusmocks "github.com/aws/amazon-ssm-agent/core/ipc/messagebus/mocks"
	discovermocks "github.com/aws/amazon-ssm-agent/core/workerprovider/longrunningprovider/discover/mocks"
	"github.com/aws/amazon-ssm-agent/core/workerprovider/longrunningprovider/model"
	providermocks "github.com/aws/amazon-ssm-agent/core/workerprovider/longrunningprovider/provider/mocks"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/suite"
)

type LongRunningProviderTestSuite struct {
	suite.Suite
	configs        map[string]*model.WorkerConfig
	pingResults    []*message.Message
	mockLog        log.T
	container      *WorkerContainer
	workerProvider *providermocks.IProvider
	workerDiscover *discovermocks.IDiscover
	messageBus     *messagebusmocks.IMessageBus
	context        *contextmocks.ICoreAgentContext
	appConfig      appconfig.SsmagentConfig
}

func (suite *LongRunningProviderTestSuite) SetupTest() {
	mockLog := logmocks.NewMockLog()
	suite.mockLog = mockLog
	suite.workerProvider = &providermocks.IProvider{}
	suite.workerDiscover = &discovermocks.IDiscover{}
	suite.messageBus = &messagebusmocks.IMessageBus{}
	suite.context = &contextmocks.ICoreAgentContext{}
	suite.appConfig = appconfig.DefaultConfig()
	suite.appConfig.Agent.LongRunningWorkerMonitorIntervalSeconds = 1

	suite.container = &WorkerContainer{
		stopWorkerMonitor: make(chan bool, 1),
		workerProvider:    suite.workerProvider,
		workerDiscover:    suite.workerDiscover,
		messageBus:        suite.messageBus,
		context:           suite.context,
	}

	suite.context.On("With", mock.Anything).Return(suite.context)
	suite.context.On("Log").Return(mockLog)
	suite.context.On("AppConfig").Return(&suite.appConfig)

	suite.configs = createStandardSSMAgentWorkers()

	result, _ := message.CreateHealthResult(model.SSMAgentWorkerName, message.LongRunning, 1)
	suite.pingResults = []*message.Message{result}

	sleep = func(duration time.Duration) {}
}

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

func (suite *LongRunningProviderTestSuite) TestStartWorkers_Successful() {
	suite.workerDiscover.On("FindWorkerConfigs").Return(suite.configs)
	suite.messageBus.On("SendSurveyMessage", mock.Anything).Return(suite.pingResults, nil)
	suite.workerProvider.On("Start", suite.configs, suite.pingResults).Return(nil)

	suite.container.Start()

	suite.workerDiscover.AssertExpectations(suite.T())
	suite.messageBus.AssertExpectations(suite.T())
	suite.workerProvider.AssertExpectations(suite.T())
}

func (suite *LongRunningProviderTestSuite) TestWatchWorkers_RestartWorker() {

	suite.workerDiscover.On("FindWorkerConfigs").Return(suite.configs)
	suite.messageBus.On("SendSurveyMessage", mock.Anything).Return(suite.pingResults, nil)
	suite.workerProvider.On("Monitor", suite.configs, suite.pingResults).Return(nil)

	go suite.container.Monitor()

	time.Sleep(2 * time.Second)
	suite.workerDiscover.AssertExpectations(suite.T())
	suite.messageBus.AssertExpectations(suite.T())
	suite.workerProvider.AssertExpectations(suite.T())
}

func (suite *LongRunningProviderTestSuite) TestStartWorkerWithTimer_StopByTimer() {

	suite.container.stopWorkerMonitor <- true
	suite.container.Monitor()

	suite.workerProvider.AssertExpectations(suite.T())
}

func (suite *LongRunningProviderTestSuite) TestStopWorker_FailureSendSurvey() {
	getPpid = func() int {
		return 1
	}
	suite.messageBus.On("SendSurveyMessage", mock.Anything).Return(nil, fmt.Errorf("SomeError")).Once()
	suite.messageBus.On("Stop").Return().Once()

	suite.container.Stop(reboot.StopTypeHardStop)

	suite.messageBus.AssertExpectations(suite.T())
	assert.True(suite.T(), <-suite.container.stopWorkerMonitor)
}

func (suite *LongRunningProviderTestSuite) TestStopWorker_SuccessSendSurvey() {
	response := []*message.Message{
		{
			SchemaVersion: 1,
			Topic:         message.GetWorkerHealthResult,
			Payload:       []byte("SomePayload"),
		},
	}
	getPpid = func() int {
		return 1
	}
	suite.messageBus.On("SendSurveyMessage", mock.Anything).Return(response, nil).Once()
	suite.messageBus.On("Stop").Return().Once()
	suite.workerProvider.AssertNotCalled(suite.T(), "KillAllWorkerProcesses")

	suite.container.Stop(reboot.StopTypeHardStop)

	suite.messageBus.AssertExpectations(suite.T())
	suite.workerProvider.AssertExpectations(suite.T())
	assert.True(suite.T(), <-suite.container.stopWorkerMonitor)
}

func (suite *LongRunningProviderTestSuite) TestStopWorker_ParentProcessZero() {
	getPpid = func() int {
		return 0
	}

	suite.messageBus.On("SendSurveyMessage", mock.Anything).Return([]*message.Message{}, nil).Once()
	suite.messageBus.On("Stop").Return().Once()
	suite.workerProvider.On("KillAllWorkerProcesses").Return().Once()

	suite.container.Stop(reboot.StopTypeHardStop)

	suite.messageBus.AssertExpectations(suite.T())
	suite.workerProvider.AssertExpectations(suite.T())

	assert.True(suite.T(), <-suite.container.stopWorkerMonitor)

}

func createStandardSSMAgentWorkers() map[string]*model.WorkerConfig {
	worker := model.WorkerConfig{
		Name: model.SSMAgentWorkerName,
		Path: appconfig.DefaultSSMAgentWorker,
	}

	configs := make(map[string]*model.WorkerConfig)
	configs[worker.Name] = &worker

	return configs
}
