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

package azure

import (
	"context"
	"fmt"
	"strings"

	"github.com/hashicorp/go-azure-helpers/lang/pointer"
	"github.com/hashicorp/go-azure-helpers/resourcemanager/commonids"
	"github.com/hashicorp/go-azure-sdk/resource-manager/storage/2023-01-01/storageaccounts"
	"github.com/hashicorp/go-azure-sdk/sdk/environments"
)

// This file is referencing the terraform-provider-azurerm: internal/services/storage/client/helpers.go

type EndpointType string

const (
	EndpointTypeBlob  = "blob"
	EndpointTypeDfs   = "dfs"
	EndpointTypeFile  = "file"
	EndpointTypeQueue = "queue"
	EndpointTypeTable = "table"
)

type AccountDetails struct {
	Kind             storageaccounts.Kind
	IsHnsEnabled     bool
	StorageAccountId commonids.StorageAccountId

	accountKey *string

	// primaryBlobEndpoint is the Primary Blob Endpoint for the Data Plane API for this Storage Account
	// e.g. `https://{account}.blob.core.windows.net`
	primaryBlobEndpoint *string

	// primaryDfsEndpoint is the Primary Dfs Endpoint for the Data Plane API for this Storage Account
	// e.g. `https://sale.dfs.core.windows.net`
	primaryDfsEndpoint *string

	// primaryFileEndpoint is the Primary File Endpoint for the Data Plane API for this Storage Account
	// e.g. `https://{account}.file.core.windows.net`
	primaryFileEndpoint *string

	// primaryQueueEndpoint is the Primary Queue Endpoint for the Data Plane API for this Storage Account
	// e.g. `https://{account}.queue.core.windows.net`
	primaryQueueEndpoint *string

	// primaryTableEndpoint is the Primary Table Endpoint for the Data Plane API for this Storage Account
	// e.g. `https://{account}.table.core.windows.net`
	primaryTableEndpoint *string
}

func (ad *AccountDetails) AccountKey(ctx context.Context, client *storageaccounts.StorageAccountsClient) (*string, error) {
	if ad.accountKey != nil {
		return ad.accountKey, nil
	}

	opts := storageaccounts.DefaultListKeysOperationOptions()
	opts.Expand = pointer.To(storageaccounts.ListKeyExpandKerb)
	listKeysResp, err := client.ListKeys(ctx, ad.StorageAccountId, opts)
	if err != nil {
		return nil, fmt.Errorf("listing Keys for %s: %+v", ad.StorageAccountId, err)
	}

	if model := listKeysResp.Model; model != nil && model.Keys != nil {
		for _, key := range *model.Keys {
			if key.Permissions == nil || key.Value == nil {
				continue
			}

			if *key.Permissions == storageaccounts.KeyPermissionFull {
				ad.accountKey = key.Value
				break
			}
		}
	}

	if ad.accountKey == nil {
		return nil, fmt.Errorf("unable to determine the Write Key for %s", ad.StorageAccountId)
	}

	return ad.accountKey, nil
}

func (ad *AccountDetails) DataPlaneEndpoint(endpointType EndpointType) (*string, error) {
	var baseUri *string
	switch endpointType {
	case EndpointTypeBlob:
		baseUri = ad.primaryBlobEndpoint

	case EndpointTypeDfs:
		baseUri = ad.primaryDfsEndpoint

	case EndpointTypeFile:
		baseUri = ad.primaryFileEndpoint

	case EndpointTypeQueue:
		baseUri = ad.primaryQueueEndpoint

	case EndpointTypeTable:
		baseUri = ad.primaryTableEndpoint

	default:
		return nil, fmt.Errorf("internal-error: unrecognised endpoint type %q when building storage client", endpointType)
	}

	if baseUri == nil {
		return nil, fmt.Errorf("determining %s endpoint for %s: missing primary endpoint", endpointType, ad.StorageAccountId)
	}
	return baseUri, nil
}

func populateAccountDetails(accountId commonids.StorageAccountId, account storageaccounts.StorageAccount) (*AccountDetails, error) {
	out := AccountDetails{
		Kind:             pointer.From(account.Kind),
		StorageAccountId: accountId,
	}

	if account.Properties == nil {
		return nil, fmt.Errorf("populating details for %s: `model.Properties` was nil", accountId)
	}
	if account.Properties.PrimaryEndpoints == nil {
		return nil, fmt.Errorf("populating details for %s: `model.Properties.PrimaryEndpoints` was nil", accountId)
	}

	props := *account.Properties
	out.IsHnsEnabled = pointer.From(props.IsHnsEnabled)

	endpoints := *props.PrimaryEndpoints
	if endpoints.Blob != nil {
		endpoint := strings.TrimSuffix(*endpoints.Blob, "/")
		out.primaryBlobEndpoint = pointer.To(endpoint)
	}
	if endpoints.Dfs != nil {
		endpoint := strings.TrimSuffix(*endpoints.Dfs, "/")
		out.primaryDfsEndpoint = pointer.To(endpoint)
	}
	if endpoints.File != nil {
		endpoint := strings.TrimSuffix(*endpoints.File, "/")
		out.primaryFileEndpoint = pointer.To(endpoint)
	}
	if endpoints.Queue != nil {
		endpoint := strings.TrimSuffix(*endpoints.Queue, "/")
		out.primaryQueueEndpoint = pointer.To(endpoint)
	}
	if endpoints.Table != nil {
		endpoint := strings.TrimSuffix(*endpoints.Table, "/")
		out.primaryTableEndpoint = pointer.To(endpoint)
	}

	return &out, nil
}

// naiveStorageAccountBlobBaseURL naively construct the storage account blob endpoint URL instead of
// learning from the storage account response. This can be incorrect if private dns zone is used.
func naiveStorageAccountBlobBaseURL(e environments.Environment, accountName string) (string, error) {
	pDomainSuffix, ok := e.Storage.DomainSuffix()
	if !ok {
		return "", fmt.Errorf("no storage domain suffix defined for environment: %s", e.Name)
	}
	return fmt.Sprintf("https://%s.blob.%s", accountName, *pDomainSuffix), nil
}
