/*
Copyright 2022 Google LLC

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.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 instanceinfo

import (
	"context"
	"path/filepath"
	"strings"

	"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/commandlineexecutor"
	"github.com/GoogleCloudPlatform/workloadagentplatform/sharedlibraries/log"
)

/*
The production library uses the ExecuteCommand function from commandlineexecutor and the
EvalSymlinks function from filepath.  We need to be able to mock these functions in our unit tests.
*/
var (
	executeCommand = commandlineexecutor.ExecuteCommand
	symLinkCommand = filepath.EvalSymlinks
)

// A PhysicalPathReader reads disk mapping information from the OS.
type PhysicalPathReader struct {
	OS string
}

const winPsPath = `C:\\Program Files\Google\google-cloud-sap-agent\google-cloud-sap-agent-diskmapping.ps1`

/*
ForDeviceName returns the physical device name of the disk mapped to "deviceName".
*/
func (r *PhysicalPathReader) ForDeviceName(ctx context.Context, deviceName string) (string, error) {
	if r.OS == "windows" {
		return forWindows(ctx, deviceName)
	}
	return forLinux(deviceName)
}

/*
forWindows returns the name of the Windows physical disk mapped to "deviceName".

Note:
google-cloud-sap-agent-diskmapping.ps1 is packaged with the gcagent binary and tested individually with its own
integration tests.
*/
func forWindows(ctx context.Context, deviceName string) (string, error) {
	result := executeCommand(ctx, commandlineexecutor.Params{
		Executable: "cmd",
		Args:       []string{"/C", "Powershell", "-File", winPsPath, deviceName},
	})
	if result.Error != nil {
		log.CtxLogger(ctx).Warnw("Could not get disk mapping for device", "name", deviceName)
		log.CtxLogger(ctx).Debugw("Execution error", "executable", winPsPath, "stdout", result.StdOut, "stderror", result.StdErr, "error", result.Error)
		return "", result.Error
	}
	m := strings.Trim(result.StdOut, "\r\n")
	log.CtxLogger(ctx).Debugw("Mapping for device", "name", deviceName, "mapping", m)
	return m, nil
}

/*
forLinux returns the name of the Linux physical disk mapped to "deviceName". (sda1, hda1, sdb1,
etc...)
*/
func forLinux(deviceName string) (string, error) {
	path, err := symLinkCommand("/dev/disk/by-id/google-" + deviceName)
	if err != nil {
		return "", err
	}

	if path != "" {
		path = strings.TrimSuffix(filepath.Base(path), "\n")
	}
	log.Logger.Debugw("Mapping for device", "name", deviceName, "mapping", path)
	return path, nil
}
