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

//go:build windows
// +build windows

package instancedetailedinformation

import (
	"encoding/json"
	"fmt"
	"os/exec"

	"strings"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/context"
	"github.com/aws/amazon-ssm-agent/agent/plugins/inventory/model"
)

const (
	CPUInfoScript = `
$wmi_proc = Get-WmiObject -Class Win32_Processor
if (@($wmi_proc)[0].NumberOfCores) #Modern OS
{
    $Sockets = @($wmi_proc).Count
    $Cores = ($wmi_proc | Measure-Object -Property NumberOfCores -Sum).Sum
    $CPUs = ($wmi_proc | Measure-Object -Property NumberOfLogicalProcessors -Sum).Sum

}
else #Legacy OS
{
    $Sockets = @($wmi_proc | Select-Object -Property SocketDesignation -Unique).Count
    $Cores = @($wmi_proc).Count
    $CPUs=$Cores
}
$CPUModel=@($wmi_proc)[0].Name
$CPUSpeed=@($wmi_proc)[0].MaxClockSpeed
if ($Cores -lt $CPUs) {
    $Hyperthread="true"
} else {
    $Hyperthread="false"
}
Write-Host -nonewline @"
{"CPUModel":"$CPUModel","CPUSpeedMHz":"$CPUSpeed","CPUs":"$CPUs","CPUSockets":"$Sockets","CPUCores":"$Cores","CPUHyperThreadEnabled":"$HyperThread"}
"@`
	OsInfoScript = `GET-WMIOBJECT -class win32_operatingsystem |
SELECT-OBJECT ServicePackMajorVersion,BuildNumber | % { Write-Output @"
{"OSServicePack":"$($_.ServicePackMajorVersion)"}
"@}`
	KernelVersionScript = `
$KernelVersion = (Join-Path $env:windir 'System32\ntoskrnl.exe' | Get-Item).VersionInfo.FileVersion
Write-Host -nonewline @"
{"KernelVersion":"$KernelVersion"}
"@`
)

var PowershellCmd = appconfig.PowerShellPluginCommandName

// decoupling exec.Command for easy testability
var cmdExecutor = executeCommand

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

// collectPlatformDependentInstanceData collects data from the system.
func collectPlatformDependentInstanceData(context context.T) (appData []model.InstanceDetailedInformation) {
	log := context.Log()
	log.Infof("Getting %v data", GathererName)
	var instanceDetailedInfo model.InstanceDetailedInformation
	err1 := collectDataFromPowershell(context, CPUInfoScript, &instanceDetailedInfo)
	err2 := collectDataFromPowershell(context, OsInfoScript, &instanceDetailedInfo)
	if err1 != nil && err2 != nil {
		// if both commands fail, return no data
		return
	}

	err3 := collectDataFromPowershell(context, KernelVersionScript, &instanceDetailedInfo)
	if err3 != nil {
		log.Warnf("Failed to gather kernel version using script: %v", KernelVersionScript)
	}

	appData = append(appData, instanceDetailedInfo)
	str, _ := json.Marshal(appData)
	log.Debugf("%v gathered: %v", GathererName, string(str))
	return
}

func collectDataFromPowershell(context context.T, powershellCommand string, instanceDetailedInfoResult *model.InstanceDetailedInformation) (err error) {
	log := context.Log()
	log.Infof("Executing command: %v", powershellCommand)
	output, err := executePowershellCommands(context, powershellCommand, "")
	if err != nil {
		log.Errorf("Error executing command - %v", err.Error())
		return
	}
	output = []byte(cleanupNewLines(string(output)))
	log.Infof("Command output: %v", string(output))

	if err = json.Unmarshal([]byte(output), instanceDetailedInfoResult); 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 cleanupNewLines(s string) string {
	return strings.Replace(strings.Replace(s, "\n", "", -1), "\r", "", -1)
}

// executePowershellCommands executes commands in powershell to get all  applications installed.
func executePowershellCommands(context context.T, command, args string) (output []byte, err error) {
	log := context.Log()
	if output, err = cmdExecutor(PowershellCmd, command+" "+args); err != nil {
		log.Debugf("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
}
