//  Copyright 2024 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
//
//      http://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 commands provides common helper methods for all commands implemented
// by CLI.
package commands

import (
	"context"
	"encoding/json"
	"fmt"
	"io"

	"github.com/GoogleCloudPlatform/galog"
	"github.com/GoogleCloudPlatform/google-guest-agent/internal/command"
	"github.com/GoogleCloudPlatform/google-guest-agent/internal/metadata"
	"github.com/spf13/cobra"
)

// TestOverrideKey is a context key to override cleanup behavior in tests.
var TestOverrideKey any = "test_override"

// sendlistener returns the command listener for send command.
func sendlistener(sendCmd *cobra.Command) (command.KnownListeners, error) {
	switch sendCmd.Parent().Name() {
	case command.ListenerGuestAgent.String():
		return command.ListenerGuestAgent, nil
	case sendCmd.CommandPath(), command.ListenerCorePlugin.String():
		return command.ListenerCorePlugin, nil
	default:
		return 0, fmt.Errorf("no known listener for command: %s", sendCmd.CommandPath())
	}
}

// NewSendCmd returns a new cobra command that implements generic send JSON command.
func NewSendCmd() *cobra.Command {
	send := &cobra.Command{
		Use:   "send <json>",
		Short: "Sends a generic JSON",
		Long:  "Sends a generic JSON. It supports both guest agent and core plugin.",
		Args:  cobra.ExactArgs(1),
		PreRun: func(cmd *cobra.Command, args []string) {
			// Disable cmd produced stdout and stderr logs in non-test env to avoid
			// duplication with galog.
			if cmd.Context().Value("enable_stdlogs") == nil {
				cmd.SetOut(io.Discard)
				cmd.SetErr(io.Discard)
			}
		},
		RunE: SendCmdRunner,
	}

	return send
}

// isValidRequest checks if request is a valid JSON as expected by command monitor.
func isValidRequest(req string) bool {
	r := &command.Request{}
	if err := json.Unmarshal([]byte(req), r); err != nil {
		return false
	}
	return true
}

// SendCmdRunner is the callback to send command.
func SendCmdRunner(cmd *cobra.Command, args []string) error {
	req := args[0]
	if !isValidRequest(req) {
		return fmt.Errorf(`invalid request: [%s %s], arg must be a valid JSON like {"Command":"echo"}`, cmd.CommandPath(), req)
	}
	lis, err := sendlistener(cmd)
	if err != nil {
		return fmt.Errorf("generic send command failed: %w", err)
	}
	galog.Debugf("Sending request [%s %s]", cmd.CommandPath(), req)
	resp := command.SendCommand(cmd.Context(), []byte(req), lis)
	galog.Infof("Command result: %s", string(resp))
	// This is used only for unit testing to capture test outputs. Command on
	// initialization sets to discard any stdout/stderr messages to avoid any
	// duplication with `galog`.
	cmd.Println(string(resp))
	return nil
}

// FetchInstanceID returns the instance ID of the instance.
func FetchInstanceID(ctx context.Context) (string, error) {
	if ctx.Value(TestOverrideKey) != nil {
		return "test", nil
	}
	return metadata.New().GetKey(ctx, "/instance/id", nil)
}
