// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package config

import (
	"crypto/tls"

	"github.com/hashicorp/consul/types"
	hcpcfg "github.com/hashicorp/hcp-sdk-go/config"
	"github.com/hashicorp/hcp-sdk-go/resource"
)

// CloudConfigurer abstracts the cloud config methods needed to connect to HCP
// in an interface for easier testing.
type CloudConfigurer interface {
	HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error)
	Resource() (resource.Resource, error)
}

// CloudConfig defines configuration for connecting to HCP services
type CloudConfig struct {
	ResourceID   string
	ClientID     string
	ClientSecret string
	Hostname     string
	AuthURL      string
	ScadaAddress string

	// Management token used by HCP management plane.
	// Cannot be set via config files.
	ManagementToken string

	// TlsConfig for testing.
	TLSConfig *tls.Config

	NodeID   types.NodeID
	NodeName string
}

func (c *CloudConfig) WithTLSConfig(cfg *tls.Config) {
	c.TLSConfig = cfg
}

func (c *CloudConfig) Resource() (resource.Resource, error) {
	return resource.FromString(c.ResourceID)
}

// HCPConfig returns a configuration to use with the HCP SDK. It assumes that the environment
// variables for the HCP configuration have already been loaded and set in the CloudConfig.
func (c *CloudConfig) HCPConfig(opts ...hcpcfg.HCPConfigOption) (hcpcfg.HCPConfig, error) {
	if c.TLSConfig == nil {
		c.TLSConfig = &tls.Config{}
	}
	if c.ClientID != "" && c.ClientSecret != "" {
		opts = append(opts, hcpcfg.WithClientCredentials(c.ClientID, c.ClientSecret))
	}
	if c.AuthURL != "" {
		opts = append(opts, hcpcfg.WithAuth(c.AuthURL, c.TLSConfig))
	}
	if c.Hostname != "" {
		opts = append(opts, hcpcfg.WithAPI(c.Hostname, c.TLSConfig))
	}
	if c.ScadaAddress != "" {
		opts = append(opts, hcpcfg.WithSCADA(c.ScadaAddress, c.TLSConfig))
	}
	opts = append(opts, hcpcfg.WithoutBrowserLogin())
	return hcpcfg.NewHCPConfig(opts...)
}

// IsConfigured returns whether the cloud configuration has been set either
// in the configuration file or via environment variables.
func (c *CloudConfig) IsConfigured() bool {
	return c.ResourceID != "" && c.ClientID != "" && c.ClientSecret != ""
}

// Merge returns a cloud configuration that is the combined the values of
// two configurations.
func Merge(o CloudConfig, n CloudConfig) CloudConfig {
	c := o
	if n.ResourceID != "" {
		c.ResourceID = n.ResourceID
	}

	if n.ClientID != "" {
		c.ClientID = n.ClientID
	}

	if n.ClientSecret != "" {
		c.ClientSecret = n.ClientSecret
	}

	if n.Hostname != "" {
		c.Hostname = n.Hostname
	}

	if n.AuthURL != "" {
		c.AuthURL = n.AuthURL
	}

	if n.ScadaAddress != "" {
		c.ScadaAddress = n.ScadaAddress
	}

	if n.ManagementToken != "" {
		c.ManagementToken = n.ManagementToken
	}

	if n.TLSConfig != nil {
		c.TLSConfig = n.TLSConfig
	}

	if n.NodeID != "" {
		c.NodeID = n.NodeID
	}

	if n.NodeName != "" {
		c.NodeName = n.NodeName
	}

	return c
}
