package account

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	dedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/constants"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/models"
	rsconfig "github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/test/resourcemanager/mockaccount"
	"github.com/tidbcloud/infra-provider/regional-server/test/resourcemanager/mockvpc"
	"go.uber.org/mock/gomock"
)

var cfg = rsconfig.RootConfig{
	ControllerConfig: rsconfig.ControllerConfig{
		RegionConfig: rsconfig.RegionConfig{
			Name: "test-region",
		},
		Env: "ft",
	},
}

func Test_findBindingAccount(t *testing.T) {
	ctl := gomock.NewController(t)
	accountrw := mockaccount.NewMockAccountReaderWriter(ctl)
	vpcr := mockvpc.NewMockVPCReader(ctl)
	accountsvc := NewService(accountrw, vpcr, cfg)
	vpcTarget := models.AccountBindingTarget{
		TargetType: constants.AccountBindingVPC,
		TargetKey:  "vpc1",
	}
	projectTarget := models.AccountBindingTarget{
		TargetType: constants.AccountBindingProject,
		TargetKey:  "project1",
	}
	tenantTarget := models.AccountBindingTarget{
		TargetType: constants.AccountBindingTenantID,
		TargetKey:  "tenant1",
	}
	t.Run("GetBinding failed", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), vpcTarget).Return(models.DataPlaneAccountInfo{}, assert.AnError).Times(1)
		_, _, err := accountsvc.findBindingAccount(context.Background(), []models.AccountBindingTarget{vpcTarget})
		assert.ErrorIs(t, err, assert.AnError)
	})
	t.Run("get vpc target success", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), vpcTarget).Return(models.DataPlaneAccountInfo{
			AWSAccountInfo: &models.AWSAccountInfo{
				AWSAccount: "account1",
				AWSRole:    "role1",
			},
		}, nil).Times(1)
		account, providerConfigName, err := accountsvc.findBindingAccount(context.Background(), []models.AccountBindingTarget{vpcTarget, projectTarget, tenantTarget})
		assert.NoError(t, err)
		assert.Equal(t, "account1", account.GetKey())
		assert.Equal(t, "account1", providerConfigName)
	})
	t.Run("get project target success", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), vpcTarget).Return(models.DataPlaneAccountInfo{}, nil).Times(1)
		accountrw.EXPECT().GetBinding(context.Background(), projectTarget).Return(models.DataPlaneAccountInfo{
			AWSAccountInfo: &models.AWSAccountInfo{
				AWSAccount: "account2",
				AWSRole:    "role2",
			},
		}, nil).Times(1)
		account, providerConfigName, err := accountsvc.findBindingAccount(context.Background(), []models.AccountBindingTarget{vpcTarget, projectTarget, tenantTarget})
		assert.NoError(t, err)
		assert.Equal(t, "account2", account.GetKey())
		assert.Equal(t, "account2", providerConfigName)
	})
	t.Run("get tenant target success", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), vpcTarget).Return(models.DataPlaneAccountInfo{}, nil).Times(1)
		accountrw.EXPECT().GetBinding(context.Background(), projectTarget).Return(models.DataPlaneAccountInfo{}, nil).Times(1)
		accountrw.EXPECT().GetBinding(context.Background(), tenantTarget).Return(models.DataPlaneAccountInfo{
			AWSAccountInfo: &models.AWSAccountInfo{
				AWSAccount: "account3",
				AWSRole:    "role3",
			},
		}, nil).Times(1)
		account, providerConfigName, err := accountsvc.findBindingAccount(context.Background(), []models.AccountBindingTarget{vpcTarget, projectTarget, tenantTarget})
		assert.NoError(t, err)
		assert.Equal(t, "account3", account.GetKey())
		assert.Equal(t, "account3", providerConfigName)
	})
}

func TestAssignAccount(t *testing.T) {
	ctl := gomock.NewController(t)
	accountrw := mockaccount.NewMockAccountReaderWriter(ctl)
	vpcr := mockvpc.NewMockVPCReader(ctl)
	accountsvc := NewService(accountrw, vpcr, cfg)
	vpcTarget := models.AccountBindingTarget{
		TargetType: constants.AccountBindingVPC,
		TargetKey:  "vpc1",
	}
	targetAccount := models.DataPlaneAccountInfo{
		AWSAccountInfo: &models.AWSAccountInfo{
			AWSAccount: "account1",
			AWSRole:    "role1",
		}}
	t.Run("findBindingAccount failed", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, assert.AnError).Times(1)
		_, _, err := accountsvc.AssignAccount(context.Background(), "vpc1", "tenant1", "project1")
		assert.ErrorIs(t, err, assert.AnError)
	})
	t.Run("findBindingAccount success", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), vpcTarget).Return(targetAccount, nil).Times(1)
		account, providerConfigName, err := accountsvc.AssignAccount(context.Background(), "vpc1", "tenant1", "project1")
		assert.NoError(t, err)
		assert.Equal(t, targetAccount, account)
		assert.Equal(t, targetAccount.GetKey(), providerConfigName)
	})
	t.Run("get default account failed", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, nil).Times(3)
		accountrw.EXPECT().GetDefault(context.Background()).Return(models.DataPlaneAccountInfo{}, assert.AnError).Times(1)
		_, _, err := accountsvc.AssignAccount(context.Background(), "vpc1", "tenant1", "project1")
		assert.ErrorIs(t, err, assert.AnError)
	})
	t.Run("bind failed", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, nil).Times(3)
		accountrw.EXPECT().GetDefault(context.Background()).Return(targetAccount, nil).Times(1)
		accountrw.EXPECT().Bind(context.Background(), targetAccount, models.AccountBindingTarget{
			TargetType: constants.AccountBindingVPC,
			TargetKey:  "vpc1",
		}).Return(assert.AnError).Times(1)
		_, _, err := accountsvc.AssignAccount(context.Background(), "vpc1", "tenant1", "project1")
		assert.ErrorIs(t, err, assert.AnError)
	})

	t.Run("bind success", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, nil).Times(3)
		accountrw.EXPECT().GetDefault(context.Background()).Return(targetAccount, nil).Times(1)
		accountrw.EXPECT().Bind(context.Background(), targetAccount, models.AccountBindingTarget{
			TargetType: constants.AccountBindingVPC,
			TargetKey:  "vpc1",
		}).Return(nil).Times(1)
		account, providerConfigName, err := accountsvc.AssignAccount(context.Background(), "vpc1", "tenant1", "project1")
		assert.Nil(t, err)
		assert.Equal(t, targetAccount, account)
		assert.Equal(t, targetAccount.GetKey(), providerConfigName)
	})
}

func TestPreAssignAccount(t *testing.T) {
	ctl := gomock.NewController(t)
	accountrw := mockaccount.NewMockAccountReaderWriter(ctl)
	vpcr := mockvpc.NewMockVPCReader(ctl)
	accountsvc := NewService(accountrw, vpcr, cfg)
	projectTarget := models.AccountBindingTarget{
		TargetType: constants.AccountBindingProject,
		TargetKey:  "project1",
	}
	targetAccount := models.DataPlaneAccountInfo{
		AWSAccountInfo: &models.AWSAccountInfo{
			AWSAccount:  "account1",
			AWSRole:     "role1",
			CMEKEnabled: true,
		}}

	req := &dedicatedv1.AssignAccountRequest{
		ProjectId:   "project1",
		CmekEnabled: true,
	}
	t.Run("findBindingAccount failed", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, assert.AnError).Times(1)
		_, err := accountsvc.PreAssignAccount(context.Background(), req)
		assert.ErrorContains(t, err, "failed to pre assign account")
	})
	t.Run("findBindingAccount success", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), projectTarget).Return(targetAccount, nil).Times(1)
		account, err := accountsvc.PreAssignAccount(context.Background(), req)
		assert.NoError(t, err)
		assert.Equal(t, &dedicatedv1.Account{AccountId: targetAccount.GetAccountID()}, account)
	})
	t.Run("list failed", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, nil).Times(1)
		accountrw.EXPECT().List(context.Background(), gomock.Any()).Return(nil, assert.AnError).Times(1)
		_, err := accountsvc.PreAssignAccount(context.Background(), req)
		assert.ErrorContains(t, err, "failed to pre assign account")
	})
	t.Run("list empty", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, nil).Times(1)
		accountrw.EXPECT().List(context.Background(), gomock.Any()).Return([]models.DataPlaneAccountInfo{}, nil).Times(1)
		_, err := accountsvc.PreAssignAccount(context.Background(), req)
		assert.ErrorContains(t, err, "failed to pre assign account")
	})
	t.Run("get default account failed", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, nil).Times(1)
		accountrw.EXPECT().List(context.Background(), gomock.Any()).Return([]models.DataPlaneAccountInfo{targetAccount}, nil).Times(1)
		accountrw.EXPECT().GetDefault(context.Background()).Return(models.DataPlaneAccountInfo{}, assert.AnError).Times(1)
		_, err := accountsvc.PreAssignAccount(context.Background(), req)
		assert.ErrorContains(t, err, "failed to pre assign account")
	})
	t.Run("default account not in list", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, nil).Times(1)
		accountrw.EXPECT().List(context.Background(), gomock.Any()).Return([]models.DataPlaneAccountInfo{targetAccount}, nil).Times(1)
		accountrw.EXPECT().GetDefault(context.Background()).Return(models.DataPlaneAccountInfo{
			AWSAccountInfo: &models.AWSAccountInfo{
				AWSAccount: "account2",
				AWSRole:    "role2",
			},
		}, nil).Times(1)
		accountrw.EXPECT().Bind(context.Background(), targetAccount, models.AccountBindingTarget{
			TargetType: constants.AccountBindingProject,
			TargetKey:  "project1",
		}).Return(nil).Times(1)
		account, err := accountsvc.PreAssignAccount(context.Background(), req)
		assert.NoError(t, err)
		assert.Equal(t, &dedicatedv1.Account{AccountId: targetAccount.GetAccountID()}, account)
	})
	t.Run("Bind failed", func(t *testing.T) {
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, nil).Times(1)
		accountrw.EXPECT().List(context.Background(), gomock.Any()).Return([]models.DataPlaneAccountInfo{targetAccount}, nil).Times(1)
		accountrw.EXPECT().GetDefault(context.Background()).Return(targetAccount, nil).Times(1)
		accountrw.EXPECT().Bind(context.Background(), targetAccount, models.AccountBindingTarget{
			TargetType: constants.AccountBindingProject,
			TargetKey:  "project1",
		}).Return(assert.AnError).Times(1)
		_, err := accountsvc.PreAssignAccount(context.Background(), req)
		assert.ErrorContains(t, err, "failed to pre assign account")
	})
	t.Run("use default account and bind success", func(t *testing.T) {
		account2 := models.DataPlaneAccountInfo{
			AWSAccountInfo: &models.AWSAccountInfo{
				AWSAccount:  "account2",
				AWSRole:     "role2",
				CMEKEnabled: true,
			},
		}
		accountrw.EXPECT().GetBinding(context.Background(), gomock.Any()).Return(models.DataPlaneAccountInfo{}, nil).Times(1)
		accountrw.EXPECT().List(context.Background(), gomock.Any()).Return([]models.DataPlaneAccountInfo{account2, targetAccount}, nil).Times(1)
		accountrw.EXPECT().GetDefault(context.Background()).Return(targetAccount, nil).Times(1)
		accountrw.EXPECT().Bind(context.Background(), targetAccount, models.AccountBindingTarget{
			TargetType: constants.AccountBindingProject,
			TargetKey:  "project1",
		}).Return(nil).Times(1)
		account, err := accountsvc.PreAssignAccount(context.Background(), req)
		assert.NoError(t, err)
		assert.Equal(t, &dedicatedv1.Account{AccountId: targetAccount.GetAccountID()}, account)
	})
}
