// 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/
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License 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 configuredaemon implements the ConfigureDaemon plugin.
package configuredaemon

import (
	"fmt"
	"path/filepath"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/context"
	"github.com/aws/amazon-ssm-agent/agent/contracts"
	"github.com/aws/amazon-ssm-agent/agent/fileutil"
	"github.com/aws/amazon-ssm-agent/agent/framework/processor/executer/iohandler"
	"github.com/aws/amazon-ssm-agent/agent/jsonutil"
	"github.com/aws/amazon-ssm-agent/agent/longrunning/manager"
	managerContracts "github.com/aws/amazon-ssm-agent/agent/longrunning/plugin"
	"github.com/aws/amazon-ssm-agent/agent/longrunning/plugin/rundaemon"
	"github.com/aws/amazon-ssm-agent/agent/task"
)

// Plugin is the type for the lrpm invoker plugin.
type Plugin struct {
	context context.T
	lrpm    manager.T
}

// NewPlugin returns lrpminvoker
func NewPlugin(context context.T) (*Plugin, error) {
	//getting the reference of LRPM - long running plugin manager - which manages all long running plugins
	lrpm, err := manager.GetInstance()
	return &Plugin{
		context: context,
		lrpm:    lrpm,
	}, err
}

func (p *Plugin) Execute(config contracts.Configuration, cancelFlag task.CancelFlag, output iohandler.IOHandler) {
	log := p.context.Log()
	log.Infof("%v started with configuration %v", Name(), config)
	if cancelFlag.ShutDown() {
		output.MarkAsShutdown()
	} else if cancelFlag.Canceled() {
		output.MarkAsCancelled()
	} else {
		runConfigureDaemon(p, p.context, config.Properties, config.OrchestrationDirectory, config.DefaultWorkingDirectory, cancelFlag, output)
	}
	return
}

func runConfigureDaemon(
	p *Plugin,
	context context.T,
	rawPluginInput interface{},
	orchestrationDir string,
	daemonWorkingDir string,
	cancelFlag task.CancelFlag,
	output iohandler.IOHandler) {

	// TODO:DAEMON: we're using the command line in a lot of places, we probably only need it in the rundaemon plugin or in the call to startplugin
	var input rundaemon.ConfigureDaemonPluginInput
	var err error
	if err = jsonutil.Remarshal(rawPluginInput, &input); err != nil {
		output.SetStatus(contracts.ResultStatusFailed)
		return
	}

	if input.PackageLocation == "" {
		input.PackageLocation = daemonWorkingDir
	}

	if err = rundaemon.ValidateDaemonInput(input); err != nil {
		output.AppendErrorf("\nconfigureDaemon input invalid: %v", err.Error())
		output.SetStatus(contracts.ResultStatusFailed)
		return
	}

	daemonFilePath := filepath.Join(appconfig.DaemonRoot, fmt.Sprintf("%v.json", input.Name))
	// make sure directory for ssm daemons exists
	if err := fileutil.MakeDirs(appconfig.DaemonRoot); err != nil {
		output.AppendErrorf("\nUnable to create ssm daemon folder %v: %v", appconfig.DaemonRoot, err.Error())
		output.SetStatus(contracts.ResultStatusFailed)
		return
	}

	switch input.Action {
	case "Start":
		// in longrunning/plugin/plugin.go (which should have the RegisteredPlugins method
		// and call a platform specific helper) as part of exploration of the appconfig.PackageRoot
		// directory tree looking for start.json (or whatever we call the daemon action)
		plugin := managerContracts.Plugin{
			Info: managerContracts.PluginInfo{
				Name:          input.Name,
				Configuration: input.Command,
				State:         managerContracts.PluginState{IsEnabled: true},
			},
			Handler: &rundaemon.Plugin{
				ExeLocation: input.PackageLocation,
				Name:        input.Name,
				CommandLine: input.Command,
				Context:     p.context,
			},
		}

		// TODO:MF: make deps file to support mocking filesystem dependency
		// Save daemon configuration file
		var errDaemonDoc error
		var ssmDaemonDoc string
		if ssmDaemonDoc, errDaemonDoc = jsonutil.Marshal(input); errDaemonDoc == nil {
			if fileutil.Exists(daemonFilePath) {
				errDaemonDoc = fileutil.DeleteFile(daemonFilePath)
			}
			if errDaemonDoc == nil {
				errDaemonDoc = fileutil.WriteAllText(daemonFilePath, ssmDaemonDoc)
			}
		}
		if errDaemonDoc != nil {
			output.AppendErrorf("\nFailed to register ssm daemon %v: %v", input.Name, errDaemonDoc.Error())
			output.SetStatus(contracts.ResultStatusFailed)
			return
		}

		p.lrpm.EnsurePluginRegistered(input.Name, plugin)
		p.lrpm.StopPlugin(input.Name, cancelFlag)
		if errStart := p.lrpm.StartPlugin(input.Name, input.Command, orchestrationDir, cancelFlag, output); errStart != nil {
			output.AppendErrorf("\nFailed to start ssm daemon %v: %v", input.Name, err.Error())
			output.SetStatus(contracts.ResultStatusFailed)
			return
		}
	case "Stop":
		if !fileutil.Exists(daemonFilePath) {
			output.AppendErrorf("\nNo ssm daemon %v exists", input.Name)
			output.SetStatus(contracts.ResultStatusFailed)
			return
		}
		err := p.lrpm.StopPlugin(input.Name, cancelFlag)
		if err != nil {
			output.AppendErrorf("\nFailed to stop ssm daemon %v: %v", input.Name, err.Error())
			output.SetStatus(contracts.ResultStatusFailed)
			return
		}
		output.AppendInfof("\nDaemon %v stopped", input.Name)
	case "Remove":
		if fileutil.Exists(daemonFilePath) {
			p.lrpm.StopPlugin(input.Name, cancelFlag)
			fileutil.DeleteFile(daemonFilePath)
			output.AppendInfof("\nDaemon %v removed", input.Name)
		} else {
			output.AppendInfof("\nDaemon %v is not installed", input.Name)
		}
	default:
		output.AppendErrorf("\nUnsupported action %v", input.Action)
		output.SetStatus(contracts.ResultStatusFailed)
		return
	}

	output.SetStatus(contracts.ResultStatusSuccess)
	return

}

// Name returns the plugin name
func Name() string {
	return appconfig.PluginNameAwsConfigureDaemon
}
