// 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 managers

import (
	"fmt"

	"github.com/aws/amazon-ssm-agent/agent/log"
	"github.com/aws/amazon-ssm-agent/agent/setupcli/managers/configurationmanager"
	"github.com/aws/amazon-ssm-agent/agent/setupcli/managers/downloadmanager"
	"github.com/aws/amazon-ssm-agent/agent/setupcli/managers/packagemanagers"
	"github.com/aws/amazon-ssm-agent/agent/setupcli/managers/registermanager"
	"github.com/aws/amazon-ssm-agent/agent/setupcli/managers/servicemanagers"
	"github.com/aws/amazon-ssm-agent/agent/setupcli/managers/verificationmanagers"
	"github.com/aws/amazon-ssm-agent/agent/updateutil/updateinfo"
)

var selectedServiceManagerCache = servicemanagers.Undefined
var selectedPackageManagerCache = packagemanagers.Undefined
var selectedVerificationManagerCache = verificationmanagers.Undefined

var getServiceManager = servicemanagers.GetServiceManager
var getPackageManager = packagemanagers.GetPackageManager
var getAllPackageManagers = packagemanagers.GetAllPackageManagers
var getVerificationManager = verificationmanagers.GetVerificationManager

func setServiceManager(log log.T) error {
	if selectedPackageManagerCache == packagemanagers.Undefined {
		// Should never happen
		panic("Package manager must be selected first before service manager")
	}
	if selectedServiceManagerCache == servicemanagers.Undefined {
		pm, ok := getPackageManager(selectedPackageManagerCache)
		if !ok {
			// Should never happen
			panic("Tried to get service manager when selected package manager does not exist")
		}

		for _, managerType := range pm.GetSupportedServiceManagers() {
			if manager, ok := getServiceManager(managerType); !ok {
				panic(fmt.Sprintf("Failed to get service manager with index %v", managerType))
			} else if manager.IsManagerEnvironment() {
				log.Infof("Selecting %s as service manager", manager.GetName())
				selectedServiceManagerCache = managerType
				return nil
			} else {
				log.Infof("Not selecting %s as service manager", manager.GetName())
			}
		}
	}

	// If we are unable to find service manager we don't want to return error
	// because package managers can support one or more service managers
	return nil
}

func setVerificationManager() error {
	if selectedPackageManagerCache == packagemanagers.Undefined {
		// Should never happen
		panic("Package manager must be selected first before service manager")
	}
	if selectedVerificationManagerCache == verificationmanagers.Undefined {
		pm, ok := getPackageManager(selectedPackageManagerCache)
		if !ok {
			// Should never happen
			panic("Tried to get verification manager when selected package manager does not exist")
		}
		selectedVerificationManagerCache = pm.GetSupportedVerificationManager()
	}

	return nil
}

func setPackageManager(log log.T) error {
	if selectedPackageManagerCache == packagemanagers.Undefined {
		allPackageManagers := getAllPackageManagers()

		// check if agent is already installed
		packageManagersNames := make([]string, 0, len(allPackageManagers))
		managerName := ""
		for _, manager := range allPackageManagers {
			packageManagersNames = append(packageManagersNames, manager.GetName())
			if manager.IsManagerEnvironment() {
				if selectedPackageManagerCache == packagemanagers.Undefined {
					// if cache is still unset, set to fist package manager that fits the environment
					selectedPackageManagerCache = manager.GetType()
					managerName = manager.GetName()
				}

				log.Debugf("Package manager %s is available, checking if agent is installed", manager.GetName())

				isInstalled, err := manager.IsAgentInstalled()
				if err != nil {
					log.Warnf("Failed to check if agent was installed using %s: %v", manager.GetName(), err)
					continue
				}

				if isInstalled {
					log.Infof("Agent is already installed with %s, selecting it as package manager", manager.GetName())
					selectedPackageManagerCache = manager.GetType()
					return nil
				}

				log.Debugf("Agent is not installed with %s", manager.GetName())
			}
		}

		if selectedPackageManagerCache == packagemanagers.Undefined {
			return fmt.Errorf("no supported package manager found in list: %v", packageManagersNames)
		} else {
			log.Infof("Selecting %s as package manager", managerName)
		}
	}

	return nil
}

// GetVerificationManager returns the selected verification manager, using cache if already selected
func GetVerificationManager() (verificationmanagers.IVerificationManager, error) {
	if err := setVerificationManager(); err != nil {
		return nil, err
	}

	if selectedVerificationManagerCache == verificationmanagers.Skip {
		return nil, nil
	} else if manager, ok := getVerificationManager(selectedVerificationManagerCache); ok {
		return manager, nil
	}

	return nil, fmt.Errorf("unable to find verification manager with index type %v", selectedServiceManagerCache)
}

// GetPackageManager returns the selected package manager, using cache if already selected
func GetPackageManager(log log.T) (packagemanagers.IPackageManager, error) {
	if err := setPackageManager(log); err != nil {
		return nil, err
	}

	manager, _ := getPackageManager(selectedPackageManagerCache)
	return manager, nil
}

// GetServiceManager returns the selected service manager, using cache if already selected.
// package manager must be selected first
func GetServiceManager(log log.T) (servicemanagers.IServiceManager, error) {
	if err := setServiceManager(log); err != nil {
		return nil, err
	}

	if manager, ok := getServiceManager(selectedServiceManagerCache); ok {
		return manager, nil
	}

	return nil, fmt.Errorf("unable to find service manager with index type %v", selectedServiceManagerCache)
}

// GetRegisterManager returns a new register manager
func GetRegisterManager() registermanager.IRegisterManager {
	return registermanager.New()
}

// GetConfigurationManager returns a new configuration manager
func GetConfigurationManager() configurationmanager.IConfigurationManager {
	return configurationmanager.New()
}

// GetDownloadManager returns a new download manager
func GetDownloadManager(log log.T, region string, manifestUrl string, updateInfo updateinfo.T, setupCLIArtifactsPath string, isNano bool, useDualStack bool) downloadmanager.IDownloadManager {
	return downloadmanager.New(log, region, manifestUrl, updateInfo, setupCLIArtifactsPath, isNano, useDualStack)
}
