package storage

import (
	"errors"
	"testing"

	alicredentials "github.com/aliyun/credentials-go/credentials"

	"github.com/tidbcloud/service-proxy/pkg/checker/core"
)

type fakeAliCredential struct {
	model *alicredentials.CredentialModel
	err   error
}

func (f *fakeAliCredential) GetAccessKeyId() (*string, error)     { return nil, nil }
func (f *fakeAliCredential) GetAccessKeySecret() (*string, error) { return nil, nil }
func (f *fakeAliCredential) GetSecurityToken() (*string, error)   { return nil, nil }
func (f *fakeAliCredential) GetBearerToken() *string              { return nil }
func (f *fakeAliCredential) GetType() *string                     { return nil }
func (f *fakeAliCredential) GetCredential() (*alicredentials.CredentialModel, error) {
	return f.model, f.err
}

func TestBuildAliCloudCredential_UsesECSRoleWithoutAssumeRole(t *testing.T) {
	originalFactory := newAliCloudCredential
	defer func() {
		newAliCloudCredential = originalFactory
	}()

	var configs []*alicredentials.Config
	base := &fakeAliCredential{
		model: &alicredentials.CredentialModel{
			AccessKeyId:     ptr("ak"),
			AccessKeySecret: ptr("sk"),
			SecurityToken:   ptr("token"),
		},
	}
	newAliCloudCredential = func(cfg *alicredentials.Config) (alicredentials.Credential, error) {
		configs = append(configs, cfg)
		return base, nil
	}

	cred, err := buildAliCloudCredential(&core.KMSParams{
		Provider: "alicloud",
		KeyID:    "key",
		Region:   "cn-hangzhou",
	}, "kms-check")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if cred != base {
		t.Fatal("expected base ecs_ram_role credential to be reused")
	}
	if len(configs) != 1 {
		t.Fatalf("expected 1 credential config, got %d", len(configs))
	}
	if configs[0].Type == nil || *configs[0].Type != "ecs_ram_role" {
		t.Fatalf("expected ecs_ram_role config, got %#v", configs[0].Type)
	}
}

func TestBuildAliCloudCredential_AssumesRoleWhenRoleARNProvided(t *testing.T) {
	originalFactory := newAliCloudCredential
	defer func() {
		newAliCloudCredential = originalFactory
	}()

	var configs []*alicredentials.Config
	base := &fakeAliCredential{
		model: &alicredentials.CredentialModel{
			AccessKeyId:     ptr("ak"),
			AccessKeySecret: ptr("sk"),
			SecurityToken:   ptr("token"),
		},
	}
	assumed := &fakeAliCredential{}
	call := 0
	newAliCloudCredential = func(cfg *alicredentials.Config) (alicredentials.Credential, error) {
		configs = append(configs, cfg)
		call++
		if call == 1 {
			return base, nil
		}
		return assumed, nil
	}

	cred, err := buildAliCloudCredential(&core.KMSParams{
		Provider: "alicloud",
		KeyID:    "key",
		Region:   "cn-hangzhou",
		AliCloud: &core.AliCloudAuth{
			RoleARN:         "acs:ram::123456789:role/test-role",
			ExternalID:      "external-id",
			RoleSessionName: "session-name",
		},
	}, "kms-check")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if cred != assumed {
		t.Fatal("expected assumed ram_role_arn credential to be returned")
	}
	if len(configs) != 2 {
		t.Fatalf("expected 2 credential configs, got %d", len(configs))
	}
	if configs[1].Type == nil || *configs[1].Type != "ram_role_arn" {
		t.Fatalf("expected ram_role_arn config, got %#v", configs[1].Type)
	}
	if configs[1].RoleArn == nil || *configs[1].RoleArn != "acs:ram::123456789:role/test-role" {
		t.Fatalf("unexpected role arn: %#v", configs[1].RoleArn)
	}
	if configs[1].ExternalId == nil || *configs[1].ExternalId != "external-id" {
		t.Fatalf("unexpected external id: %#v", configs[1].ExternalId)
	}
	if configs[1].RoleSessionName == nil || *configs[1].RoleSessionName != "session-name" {
		t.Fatalf("unexpected role session name: %#v", configs[1].RoleSessionName)
	}
}

func TestBuildAliCloudCredential_DefaultsRoleSessionName(t *testing.T) {
	originalFactory := newAliCloudCredential
	defer func() {
		newAliCloudCredential = originalFactory
	}()

	base := &fakeAliCredential{
		model: &alicredentials.CredentialModel{
			AccessKeyId:     ptr("ak"),
			AccessKeySecret: ptr("sk"),
			SecurityToken:   ptr("token"),
		},
	}
	var assumedCfg *alicredentials.Config
	call := 0
	newAliCloudCredential = func(cfg *alicredentials.Config) (alicredentials.Credential, error) {
		call++
		if call == 1 {
			return base, nil
		}
		assumedCfg = cfg
		return &fakeAliCredential{}, nil
	}

	_, err := buildAliCloudCredential(&core.KMSParams{
		Provider: "alicloud",
		KeyID:    "key",
		Region:   "cn-hangzhou",
		AliCloud: &core.AliCloudAuth{
			RoleARN: "acs:ram::123456789:role/test-role",
		},
	}, "audit-check")
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if assumedCfg == nil || assumedCfg.RoleSessionName == nil || *assumedCfg.RoleSessionName != "audit-check" {
		t.Fatalf("unexpected default session name: %#v", assumedCfg)
	}
}

func TestBuildAliCloudCredential_RequiresBaseSecurityTokenForAssumeRole(t *testing.T) {
	originalFactory := newAliCloudCredential
	defer func() {
		newAliCloudCredential = originalFactory
	}()

	newAliCloudCredential = func(cfg *alicredentials.Config) (alicredentials.Credential, error) {
		return &fakeAliCredential{
			model: &alicredentials.CredentialModel{
				AccessKeyId:     ptr("ak"),
				AccessKeySecret: ptr("sk"),
			},
		}, nil
	}

	_, err := buildAliCloudCredential(&core.KMSParams{
		Provider: "alicloud",
		KeyID:    "key",
		Region:   "cn-hangzhou",
		AliCloud: &core.AliCloudAuth{
			RoleARN: "acs:ram::123456789:role/test-role",
		},
	}, "kms-check")
	if err == nil || err.Error() != "ecs role credential missing security token" {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestBuildAliCloudCredential_PropagatesBaseCredentialError(t *testing.T) {
	originalFactory := newAliCloudCredential
	defer func() {
		newAliCloudCredential = originalFactory
	}()

	newAliCloudCredential = func(cfg *alicredentials.Config) (alicredentials.Credential, error) {
		return nil, errors.New("boom")
	}

	_, err := buildAliCloudCredential(&core.KMSParams{
		Provider: "alicloud",
		KeyID:    "key",
		Region:   "cn-hangzhou",
	}, "kms-check")
	if err == nil || err.Error() != "failed to create ecs role credential: boom" {
		t.Fatalf("unexpected error: %v", err)
	}
}
