// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1

package oci

import (
	"fmt"
	"path"
	"strings"

	"github.com/hashicorp/terraform/internal/backend"
	"github.com/hashicorp/terraform/internal/backend/backendbase"
	"github.com/hashicorp/terraform/internal/configs/configschema"
	"github.com/hashicorp/terraform/internal/tfdiags"
	"github.com/zclconf/go-cty/cty"
)

var (
	lockFileSuffix = ".lock"
)

func New() backend.Backend {
	return &Backend{}
}

// New creates a new backend for oci remote state.
func (b *Backend) ConfigSchema() *configschema.Block {
	return &configschema.Block{
		Attributes: map[string]*configschema.Attribute{
			KeyAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "The name of the state file stored on the remote backend.",
			},
			BucketAttrName: {
				Type:        cty.String,
				Required:    true,
				Description: "The name of the OCI Object Storage bucket.",
			},
			NamespaceAttrName: {
				Type:        cty.String,
				Required:    true,
				Description: "The namespace of the OCI Object Storage.",
			},
			RegionAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "OCI region where the bucket is located.",
			},
			TenancyOcidAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "The OCID of the tenancy.",
			},
			UserOcidAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "The OCID of the user.",
			},
			FingerprintAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "The fingerprint of the user's API key.",
			},
			PrivateKeyAttrName: {
				Type:        cty.String,
				Sensitive:   true,
				Optional:    true,
				Description: "The private key for API authentication.",
			},
			PrivateKeyPathAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "Path to the private key file.",
			},
			PrivateKeyPasswordAttrName: {
				Type:        cty.String,
				Sensitive:   true,
				Optional:    true,
				Description: "Passphrase for the private key, if required.",
			},
			AuthAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "Authentication method (API key, Instance Principal, Resource Principal, etc.).",
			},

			ConfigFileProfileAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "Profile name from the OCI config file.",
			},
			WorkspaceKeyPrefixAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "The prefix applied to the non-default state path inside the bucket.",
			},
			KmsKeyIdAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "The OCID of a master encryption key used to call the Key Management service to generate a data encryption key or to encrypt or decrypt a data encryption key.",
			},
			SseCustomerKeyAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "The optional header that specifies the base64-encoded 256-bit encryption key to use to encrypt or decrypt the data.",
				Sensitive:   true,
			},
			SseCustomerKeySHA256AttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "The optional header that specifies the base64-encoded SHA256 hash of the encryption key. This value is used to check the integrity of the encryption key.",
			},
			SseCustomerAlgorithmAttrName: {
				Type:        cty.String,
				Optional:    true,
				Description: "The optional header that specifies \"AES256\" as the encryption algorithm.",
			},
		},
	}
}

type Backend struct {
	configProvider       ociAuthConfigProvider
	bucket               string
	key                  string
	namespace            string
	workspaceKeyPrefix   string
	kmsKeyID             string
	SSECustomerKey       string
	SSECustomerKeySHA256 string
	SSECustomerAlgorithm string
	client               *RemoteClient
}

func (b *Backend) PrepareConfig(obj cty.Value) (cty.Value, tfdiags.Diagnostics) {

	var diags tfdiags.Diagnostics
	if obj.IsNull() {

		diags.Append(tfdiags.AttributeValue(tfdiags.Error, "Invalid Configuration", "Received null configuration for OCI backend.", cty.GetAttrPath(".")))
		return obj, diags
	}
	if bucketVal := obj.GetAttr(BucketAttrName); bucketVal.IsNull() || bucketVal.AsString() == "" {
		diags = diags.Append(requiredAttributeErrDiag(cty.GetAttrPath(BucketAttrName)))
	} else {
		validateStringBucketName(bucketVal.AsString(), cty.GetAttrPath(BucketAttrName), &diags)
	}
	if namespaceVal := obj.GetAttr(NamespaceAttrName); namespaceVal.IsNull() || namespaceVal.AsString() == "" {
		diags = diags.Append(requiredAttributeErrDiag(cty.GetAttrPath(NamespaceAttrName)))
	}
	if keyVal, ok := getBackendAttrWithDefault(obj, KeyAttrName, defaultKeyValue); ok {
		validateStringObjectPath(keyVal.AsString(), cty.GetAttrPath(KeyAttrName), &diags)
	}
	if workspaceKeyPrefixVal, ok := getBackendAttrWithDefault(obj, WorkspaceKeyPrefixAttrName, defaultWorkspaceEnvPrefix); ok {
		validateStringWorkspacePrefix(workspaceKeyPrefixVal.AsString(), cty.GetAttrPath(WorkspaceKeyPrefixAttrName), &diags)
	}
	authVal, ok := getBackendAttr(obj, AuthAttrName)
	if ok && len(authVal.AsString()) > 0 {

		switch strings.ToLower(authVal.AsString()) {
		case strings.ToLower(AuthAPIKeySetting):
			//Nothing to do
			return obj, diags
		case strings.ToLower(AuthInstancePrincipalSetting), strings.ToLower(AuthInstancePrincipalWithCertsSetting), strings.ToLower(ResourcePrincipal), strings.ToLower(AuthSecurityToken):
			region, _ := getBackendAttr(obj, RegionAttrName)
			if region.AsString() == "" {
				diags = diags.Append(tfdiags.AttributeValue(tfdiags.Error,
					"Missing region attribute required",
					fmt.Sprintf("The attribute %q is required by the backend for %s authentication.\n\n", RegionAttrName, authVal.AsString()), cty.GetAttrPath(RegionAttrName),
				))
			}
			if strings.ToLower(authVal.AsString()) == strings.ToLower(AuthSecurityToken) {
				profileVal, _ := getBackendAttr(obj, ConfigFileProfileAttrName)
				if profileVal.IsNull() || profileVal.AsString() == "" {
					diags = diags.Append(tfdiags.AttributeValue(tfdiags.Error,
						"Missing config_file_profile attribute required",
						fmt.Sprintf("The attribute %q is required by the backend for %s authentication.\n\n", ConfigFileProfileAttrName, authVal.AsString()), cty.GetAttrPath(ConfigFileProfileAttrName),
					))
				}
			}
		default:
			diags = diags.Append(tfdiags.AttributeValue(tfdiags.Error,
				"Invalid authentication method",
				fmt.Sprintf("auth must be one of '%s' or '%s' or '%s' or '%s' or '%s' or '%s'", AuthAPIKeySetting, AuthInstancePrincipalSetting, AuthInstancePrincipalWithCertsSetting, AuthSecurityToken, ResourcePrincipal, AuthOKEWorkloadIdentity), cty.GetAttrPath(AuthAttrName),
			))
		}
	}

	customerKey, _ := getBackendAttr(obj, SseCustomerKeyAttrName)
	customerKeySHA, _ := getBackendAttr(obj, SseCustomerKeySHA256AttrName)
	kmsKeyId, _ := getBackendAttr(obj, KmsKeyIdAttrName)
	privateKey, _ := getBackendAttr(obj, PrivateKeyAttrName)
	privateKeyPath, _ := getBackendAttr(obj, PrivateKeyPathAttrName)

	if (!customerKey.IsNull() && len(customerKey.AsString()) > 0) && (customerKeySHA.IsNull() || len(customerKeySHA.AsString()) == 0) {
		diags = diags.Append(attributeErrDiag(
			"Invalid Attribute Combination",
			`  sse_customer_key and its SHA both required.`,
			cty.GetAttrPath(SseCustomerKeySHA256AttrName)))
	}
	if !customerKey.IsNull() && len(customerKey.AsString()) > 0 && !kmsKeyId.IsNull() && len(kmsKeyId.AsString()) > 0 {
		diags = diags.Append(attributeErrDiag(
			"Invalid Attribute Combination",
			`Only one of kms_key_id, sse_customer_key can be set.`,
			cty.GetAttrPath(KmsKeyIdAttrName),
		))
	}
	if !privateKey.IsNull() && len(privateKey.AsString()) > 0 && !privateKeyPath.IsNull() && len(privateKeyPath.AsString()) > 0 {
		diags = diags.Append(attributeErrDiag(
			"Invalid Attribute Combination",
			`Only one of private_key, private_key_path can be set.`,
			cty.GetAttrPath(PrivateKeyPathAttrName),
		))
	}
	return obj, diags
}
func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
	var diags tfdiags.Diagnostics

	if bucketVal, ok := getBackendAttr(obj, BucketAttrName); ok {
		b.bucket = bucketVal.AsString()
	}
	if namespaceVal, ok := getBackendAttr(obj, NamespaceAttrName); ok {
		b.namespace = namespaceVal.AsString()
	}
	if keyVal, ok := getBackendAttrWithDefault(obj, KeyAttrName, defaultKeyValue); ok {
		b.key = keyVal.AsString()
	}

	if workspaceKeyPrefixVal, ok := getBackendAttrWithDefault(obj, WorkspaceKeyPrefixAttrName, defaultWorkspaceEnvPrefix); ok {
		b.workspaceKeyPrefix = workspaceKeyPrefixVal.AsString()
	}

	if kmsKeyIdVal, ok := getBackendAttr(obj, KmsKeyIdAttrName); ok {
		b.kmsKeyID = kmsKeyIdVal.AsString()
	}
	if customerKeyVal, ok := getBackendAttr(obj, SseCustomerKeyAttrName); ok {
		b.SSECustomerKey = customerKeyVal.AsString()
	}
	if customerKeySHA256Val, ok := getBackendAttr(obj, SseCustomerKeySHA256AttrName); ok {
		b.SSECustomerKeySHA256 = customerKeySHA256Val.AsString()
	}
	if customerAlgorithmVal, ok := getBackendAttrWithDefault(obj, SseCustomerAlgorithmAttrName, DefaultAlgorithm); ok {
		b.SSECustomerAlgorithm = customerAlgorithmVal.AsString()
	}
	b.configProvider = newOciAuthConfigProvider(obj)

	err := b.configureRemoteClient()
	if err != nil {
		diags = append(diags, backendbase.ErrorAsDiagnostics(err)[0])
	}
	return diags
}

func (b *Backend) path(name string) string {
	if name == backend.DefaultStateName {
		return b.key
	}

	return path.Join(b.workspaceKeyPrefix, name, b.key)
}

// getLockFilePath returns the path to the lock file for the given Terraform state.
// For `default.tfstate`, the lock file is stored at `default.tfstate.tflock`.
func (b *Backend) getLockFilePath(name string) string {
	return b.path(name) + lockFileSuffix
}
