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

package auth

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

	"github.com/Azure/azure-sdk-for-go/sdk/azcore"
	"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
	"github.com/opentofu/opentofu/internal/tfdiags"
)

type AzureCLIAuthConfig struct {
	CLIAuthEnabled bool
}

type azureCLICredentialAuth struct{}

var _ AuthMethod = &azureCLICredentialAuth{}

func (cred *azureCLICredentialAuth) Name() string {
	return "Azure CLI Auth"
}

func (cred *azureCLICredentialAuth) Construct(_ context.Context, config *Config) (azcore.TokenCredential, error) {
	// The SubscriptionID and TenantID can be empty, and the logic of this will still be okay
	return azidentity.NewAzureCLICredential(&azidentity.AzureCLICredentialOptions{
		Subscription: config.StorageAddresses.SubscriptionID,
		TenantID:     config.StorageAddresses.TenantID,
	})
}

func (cred *azureCLICredentialAuth) Validate(ctx context.Context, config *Config) tfdiags.Diagnostics {
	var diags tfdiags.Diagnostics
	if !config.CLIAuthEnabled {
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"Invalid Azure Command Line Auth",
			"Setting use_cli to false prevents the use of command-line auth (az).",
		))
		return diags
	}
	_, err := exec.LookPath("az")
	if err != nil {
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"Invalid Azure Command Line Auth",
			"Error looking for command az in your PATH. Make sure the Azure Command Line tool is installed and executable.",
		))
		return diags
	}
	// Make sure the user is logged in by attempting to get the subscription
	_, err = getCurrentSubscriptionInfo(ctx)
	if err != nil {
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"Invalid Azure Command Line Auth",
			fmt.Sprintf("Error using the az command: %s.", tfdiags.FormatError(err)),
		))
	}
	return diags
}

func (cred *azureCLICredentialAuth) AugmentConfig(ctx context.Context, config *Config) (err error) {
	if config.StorageAddresses.SubscriptionID == "" {
		config.StorageAddresses.SubscriptionID, err = getCliAzureSubscriptionID(ctx)
		if err != nil {
			return err
		}
	}
	return checkNamesForAccessKeyCredentials(config.StorageAddresses)
}

type Subscription struct {
	Id        string `json:"id"`
	Name      string `json:"name"`
	IsDefault bool   `json:"isDefault"`
}

// getCliAzureSubscriptionID obtains the subscription ID currently active in the
// Azure profile. This assumes the user has the Azure CLI installed on their machine.
func getCliAzureSubscriptionID(ctx context.Context) (string, error) {
	rawSubscription, err := getCurrentSubscriptionInfo(ctx)
	if err != nil {
		return "", err
	}

	var subscription Subscription
	err = json.Unmarshal(rawSubscription, &subscription)
	if err != nil {
		return "", fmt.Errorf("json error for azure subscription: %w", err)
	}

	return subscription.Id, nil
}

// getCurrentSubscriptionInfo is adapted from azure-sdk-for-go's CLI token retrieval
func getCurrentSubscriptionInfo(ctx context.Context) ([]byte, error) {
	cliCmd := exec.CommandContext(ctx, "az", "account", "show", "-o", "json")
	var stderr bytes.Buffer
	cliCmd.Stderr = &stderr

	stdout, err := cliCmd.Output()
	if err != nil {
		msg := stderr.String()
		return nil, fmt.Errorf("error getting subscription info: error: %w\nmore information: %s", err, msg)
	}

	return stdout, nil
}
