// 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 app represents the core SSM agent object
package app

import (
	"runtime"
	"time"

	agentcontracts "github.com/aws/amazon-ssm-agent/agent/contracts"
	"github.com/aws/amazon-ssm-agent/agent/version"

	"github.com/aws/amazon-ssm-agent/common/telemetry"
	"github.com/aws/amazon-ssm-agent/core/app/context"
	"github.com/aws/amazon-ssm-agent/core/app/credentialrefresher"
	reboot "github.com/aws/amazon-ssm-agent/core/app/reboot/model"
	"github.com/aws/amazon-ssm-agent/core/app/registrar"
	"github.com/aws/amazon-ssm-agent/core/app/selfupdate"
	"github.com/aws/amazon-ssm-agent/core/ipc/messagebus"
	"github.com/aws/amazon-ssm-agent/core/workerprovider/longrunningprovider"
)

type CoreAgent interface {
	Start(statusChan *agentcontracts.StatusComm) error
	Stop()
}

// SSMCoreAgent encapsulates the core functionality of the agent
type SSMCoreAgent struct {
	context        context.ICoreAgentContext
	container      longrunningprovider.IContainer
	selfupdate     selfupdate.ISelfUpdate
	credsRefresher credentialrefresher.ICredentialRefresher
	registrar      registrar.IRetryableRegistrar
}

// NewSSMCoreAgent creates and returns and object of type CoreAgent interface
func NewSSMCoreAgent(context context.ICoreAgentContext, messageBus messagebus.IMessageBus) CoreAgent {
	coreAgent := &SSMCoreAgent{
		context:        context,
		container:      longrunningprovider.NewWorkerContainer(context, messageBus),
		selfupdate:     selfupdate.NewSelfUpdater(context),
		credsRefresher: credentialrefresher.NewCredentialRefresher(context),
	}

	if registrar := registrar.NewRetryableRegistrar(context); registrar != nil {
		coreAgent.registrar = registrar
	}

	return coreAgent
}

// Start the core manager
func (agent *SSMCoreAgent) Start(statusChan *agentcontracts.StatusComm) error {
	log := agent.context.Log()

	log.Infof("amazon-ssm-agent - %v", version.String())
	log.Infof("OS: %s, Arch: %s", runtime.GOOS, runtime.GOARCH)
	log.Info("Starting Core Agent")

	if agent.registrar != nil {
		log.Info("Registrar detected. Attempting registration")
		if err := agent.registrar.Start(); err != nil {
			return err
		}

		select {
		case <-agent.registrar.GetRegistrationAttemptedChan():
			log.Info("Registration attempted. Resuming core agent startup.")
			break
		case <-statusChan.TerminationChan:
			log.Info("Received stop/termination signal from main routine")
			statusChan.DoneChan <- struct{}{}
			return nil
		}
	}

	if err := agent.credsRefresher.Start(); err != nil {
		return err
	}

	credentialsReadyChan := agent.credsRefresher.GetCredentialsReadyChan()
	select {
	case <-credentialsReadyChan:
		log.Debug("Agent core module started after receiving credentials")
		close(credentialsReadyChan)
		agent.container.Start()
		go agent.container.Monitor()
		agent.selfupdate.Start()
		// removing the below wait time will cause the agent worker to run orphaned when
		// agent is stopped immediately after start
		time.Sleep(3 * time.Second)
		break
	case <-statusChan.TerminationChan:
		log.Info("Received stop/termination signal from main routine")
		break
	}

	statusChan.DoneChan <- struct{}{}
	return nil
}

// Stop the core manager
func (agent *SSMCoreAgent) Stop() {
	log := agent.context.Log()
	log.Info("Stopping Core Agent")
	log.Flush()

	agent.selfupdate.Stop()
	agent.container.Stop(reboot.StopTypeHardStop)
	agent.credsRefresher.Stop()
	if agent.registrar != nil {
		agent.registrar.Stop()
	}

	telemetry.Shutdown()

	log.Info("Bye.")
	log.Flush()
}
