package azure

import (
	"context"
	"fmt"
	"net"
	"os"
	"strconv"
	"testing"
	"time"

	"github.com/hashicorp/vault/helper/testhelpers/azurite"

	"github.com/Azure/azure-storage-blob-go/azblob"
	log "github.com/hashicorp/go-hclog"
	"github.com/hashicorp/vault/sdk/helper/logging"
	"github.com/hashicorp/vault/sdk/physical"
)

/// These tests run against an Azurite docker container, unless AZURE_ACCOUNT_NAME is given.
/// Authentication options:
/// - Use a static access key via AZURE_ACCOUNT_KEY
/// - Use managed identities (leave AZURE_ACCOUNT_KEY empty)
///
/// To run the tests using managed identities, the following pre-requisites have to be met:
/// 1. Access to the Azure Instance Metadata Service (IMDS) is required (e.g. run it on a Azure VM)
/// 2. A system-assigned oder user-assigned identity attached to the host running the test
/// 3. A role assignment for a storage account with "Storage Blob Data Contributor" permissions

func testFixture(t *testing.T) (*AzureBackend, func()) {
	t.Helper()

	ts := time.Now().UnixNano()
	name := fmt.Sprintf("vault-test-%d", ts)
	_ = os.Setenv("AZURE_BLOB_CONTAINER", name)

	cleanup := func() {}
	backendConf := map[string]string{
		"container": name,
	}

	if os.Getenv("AZURE_ACCOUNT_NAME") == "" {
		dockerCleanup, conf := azurite.PrepareTestContainer(t, "")
		cfgaz := conf.(*azurite.Config)
		backendConf["accountName"] = cfgaz.AccountName
		backendConf["accountKey"] = cfgaz.AccountKey
		backendConf["testHost"] = cfgaz.Endpoint
		cleanup = dockerCleanup
	} else {
		accountKey := os.Getenv("AZURE_ACCOUNT_KEY")
		if accountKey != "" {
			t.Log("using account key provided to authenticate against storage account")
		} else {
			t.Log("using managed identity to authenticate against storage account")
			if !isIMDSReachable(t) {
				t.Log("running managed identity test requires access to the Azure IMDS with a valid identity for a storage account attached to it, skipping")
				t.SkipNow()
			}
		}
	}

	backend, err := NewAzureBackend(backendConf, logging.NewVaultLogger(log.Debug))
	if err != nil {
		defer cleanup()
		t.Fatalf("err: %s", err)
	}

	azBackend := backend.(*AzureBackend)

	return azBackend, func() {
		blobService, err := azBackend.container.GetProperties(context.Background(), azblob.LeaseAccessConditions{})
		if err != nil {
			t.Logf("failed to retrieve blob container info: %v", err)
			return
		}

		if blobService.StatusCode() == 200 {
			_, err := azBackend.container.Delete(context.Background(), azblob.ContainerAccessConditions{})
			if err != nil {
				t.Logf("clean up failed: %v", err)
			}
		}
		cleanup()
	}
}

func TestAzureBackend(t *testing.T) {
	backend, cleanup := testFixture(t)
	defer cleanup()

	physical.ExerciseBackend(t, backend)
	physical.ExerciseBackend_ListPrefix(t, backend)
}

func TestAzureBackend_ListPaging(t *testing.T) {
	backend, cleanup := testFixture(t)
	defer cleanup()

	// by default, azure returns 5000 results in a page, load up more than that
	for i := 0; i < MaxListResults+100; i++ {
		if err := backend.Put(context.Background(), &physical.Entry{
			Key:   "foo" + strconv.Itoa(i),
			Value: []byte(strconv.Itoa(i)),
		}); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	results, err := backend.List(context.Background(), "")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	if len(results) != MaxListResults+100 {
		t.Fatalf("expected %d, got %d, %v", MaxListResults+100, len(results), results)
	}
}

func isIMDSReachable(t *testing.T) bool {
	t.Helper()

	_, err := net.DialTimeout("tcp", "169.254.169.254:80", time.Second*3)
	if err != nil {
		return false
	}

	return true
}
