// Copyright 2017 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 registry

import (
	"encoding/json"
	"errors"
	"fmt"

	"github.com/google/uuid"

	"os/exec"

	"path/filepath"
	"strings"

	"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/plugins/inventory/model"
	"github.com/aws/amazon-ssm-agent/agent/plugins/pluginutil"
)

const (
	MaxValueCountLimit = 250
	ValueLimitExceeded = "ValueLimitExceeded"
)

type filterObj struct {
	Path       string
	Recursive  bool
	ValueNames []string
}

var PowershellCmd = appconfig.PowerShellPluginCommandName

var ValueCountLimitExceeded = errors.New("Exceeded register value count limit")

// LogError is a wrapper on log.Error for easy testability
func LogError(log log.T, err error) {
	// To debug unit test, please uncomment following line
	// fmt.Println(err)
	log.Error(err)
}

func randomString(length int) string {
	return uuid.New().String()[:length]
}

var cmdExecutor = executeCommand

func executeCommand(command string, args ...string) ([]byte, error) {
	return exec.Command(command, args...).CombinedOutput()
}

// executePowershellCommands executes commands in Powershell to get registry values.
func executePowershellCommands(log log.T, command, args string) (output []byte, err error) {
	if output, err = cmdExecutor(PowershellCmd, command+" "+args); err != nil {
		log.Errorf("Failed to execute command : %v %v with error - %v",
			command,
			args,
			err.Error())
		log.Debugf("Command Stderr: %v", string(output))
		err = fmt.Errorf("Command failed with error: %v", string(output))
	}

	return
}

func collectDataFromPowershell(log log.T, powershellCommand string, registryInfo *[]model.RegistryData) (err error) {
	log.Infof("Executing command: %v", powershellCommand)
	var output []byte
	var cleanOutput string
	output, err = executePowershellCommands(log, powershellCommand, "")
	if err != nil {
		log.Errorf("Error executing command - %v", err.Error())
		return
	}
	log.Debugf("Before cleanup %v", string(output))
	cleanOutput, err = pluginutil.ReplaceMarkedFields(pluginutil.CleanupNewLines(string(output)), startMarker, endMarker, pluginutil.CleanupJSONField)
	if err != nil {
		LogError(log, err)
		return
	}

	log.Debugf("Command output: %v", string(cleanOutput))
	if cleanOutput == ValueLimitExceeded {
		log.Error("Number of values collected exceeded limit")
		err = ValueCountLimitExceeded
		return
	}
	if err = json.Unmarshal([]byte(cleanOutput), registryInfo); err != nil {
		err = fmt.Errorf("Unable to parse command output - %v", err.Error())
		log.Error(err.Error())
		log.Infof("Error parsing command output - no data to return")
	}
	return
}

func collectRegistryData(context context.T, config model.Config) (data []model.RegistryData, err error) {
	log := context.Log()
	log.Infof("collectRegistryData called")
	config.Filters = strings.Replace(config.Filters, `\`, `/`, -1)
	var filterList []filterObj
	if err = json.Unmarshal([]byte(config.Filters), &filterList); err != nil {
		return
	}

	valueScanLimit := MaxValueCountLimit
	for _, filter := range filterList {
		var temp []model.RegistryData

		path := filepath.FromSlash(filter.Path)
		recursive := filter.Recursive
		valueNames := filter.ValueNames
		log.Infof("valueNames %v", valueNames)
		registryPath := "Registry::" + path
		execScript := registryInfoScript + "-Path \"" + registryPath + "\" -ValueLimit " + fmt.Sprint(valueScanLimit)
		if recursive == true {
			execScript += " -Recursive"
		}
		if valueNames != nil && len(valueNames) > 0 {
			valueNames = pluginutil.AddSingleQuotesToStringArray(valueNames)
			log.Infof("Formatted valueNames %v", valueNames)
			valueNamesArg := strings.Join(valueNames, ",")
			execScript += " -Values " + valueNamesArg
		}

		if getRegistryErr := collectDataFromPowershell(log, execScript, &temp); getRegistryErr != nil {
			LogError(log, getRegistryErr)
			if getRegistryErr == ValueCountLimitExceeded {
				err = getRegistryErr
				return
			}
			continue
		}

		data = append(data, temp...)
		valueScanLimit = MaxValueCountLimit - len(data)
	}
	log.Infof("Collected %d registry entries", len(data))
	return
}
