// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package command

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/exec"

	"github.com/hashicorp/go-plugin"

	"github.com/opentofu/opentofu/internal/cloudplugin"
	"github.com/opentofu/opentofu/internal/cloudplugin/cloudplugin1"
	"github.com/opentofu/opentofu/internal/logging"
)

// CloudCommand is a Command implementation that interacts with Terraform
// Cloud for operations that are inherently planless. It delegates
// all execution to an internal plugin.
type CloudCommand struct {
	Meta
}

const (
	// DefaultCloudPluginVersion is the implied protocol version, though all
	// historical versions are defined explicitly.
	DefaultCloudPluginVersion = 1

	// ExitRPCError is the exit code that is returned if an plugin
	// communication error occurred.
	ExitRPCError = 99
)

var (
	// Handshake is used to verify that the plugin is the appropriate plugin for
	// the client. This is not a security verification.
	Handshake = plugin.HandshakeConfig{
		MagicCookieKey:   "TF_CLOUDPLUGIN_MAGIC_COOKIE",
		MagicCookieValue: "721fca41431b780ff3ad2623838faaa178d74c65e1cfdfe19537c31656496bf9f82d6c6707f71d81c8eed0db9043f79e56ab4582d013bc08ead14f57961461dc",
		ProtocolVersion:  DefaultCloudPluginVersion,
	}
)

func (c *CloudCommand) proxy(args []string, stdout, stderr io.Writer) int {
	client := plugin.NewClient(&plugin.ClientConfig{
		HandshakeConfig:  Handshake,
		AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
		Cmd:              exec.Command("./terraform-cloudplugin"),
		Logger:           logging.NewCloudLogger(),
		VersionedPlugins: map[int]plugin.PluginSet{
			1: {
				"cloud": &cloudplugin1.GRPCCloudPlugin{},
			},
		},
	})
	defer client.Kill()

	// Connect via RPC
	rpcClient, err := client.Client()
	if err != nil {
		fmt.Fprintf(stderr, "Failed to create cloud plugin client: %s", err)
		return ExitRPCError
	}

	// Request the plugin
	raw, err := rpcClient.Dispense("cloud")
	if err != nil {
		fmt.Fprintf(stderr, "Failed to request cloud plugin interface: %s", err)
		return ExitRPCError
	}

	// Proxy the request
	// Note: future changes will need to determine the type of raw when
	// multiple versions are possible.
	cloud1, ok := raw.(cloudplugin.Cloud1)
	if !ok {
		c.Ui.Error("If more than one cloudplugin versions are available, they need to be added to the cloud command. This is a bug in OpenTofu.")
		return ExitRPCError
	}
	return cloud1.Execute(args, stdout, stderr)
}

// Run runs the cloud command with the given arguments.
func (c *CloudCommand) Run(args []string) int {
	args = c.Meta.process(args)

	// TODO: Download and verify the signing of the terraform-cloudplugin
	// release that is appropriate for this OS/Arch
	if _, err := os.Stat("./terraform-cloudplugin"); err != nil {
		c.Ui.Warn("terraform-cloudplugin not found. This plugin does not have an official release yet.")
		return 1
	}

	// TODO: Need to use some type of c.Meta handle here
	return c.proxy(args, os.Stdout, os.Stderr)
}

// Help returns help text for the cloud command.
func (c *CloudCommand) Help() string {
	helpText := new(bytes.Buffer)
	c.proxy([]string{}, helpText, io.Discard)

	return helpText.String()
}

// Synopsis returns a short summary of the cloud command.
func (c *CloudCommand) Synopsis() string {
	return "Manage cloud backend settings and metadata"
}
