package resourcepool

import (
	"context"
	"testing"

	"github.com/samber/lo"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/billing"
	mockbilling "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/billing/mock_billing"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	mockregion "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/region/mock_region"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func TestCheckTenantBillingPlan(t *testing.T) {
	tests := []struct {
		name            string
		result          *billing.ActionCheckResult
		billingErr      bizerr.BizError
		expectedErrCode bizerr.ErrCode
		expectedErrMsg  string
	}{
		{
			name:   "allowed",
			result: &billing.ActionCheckResult{Pass: true},
		},
		{
			name:            "rejected with reason",
			result:          &billing.ActionCheckResult{Reason: lo.ToPtr("account has no credit")},
			expectedErrCode: bizerr.ErrCodeParams,
			expectedErrMsg:  "account has no credit",
		},
		{
			name:            "rejected without reason",
			result:          &billing.ActionCheckResult{},
			expectedErrCode: bizerr.ErrCodeParams,
			expectedErrMsg:  "Check tenant billing plan failed",
		},
		{
			name:            "billing service failure",
			billingErr:      bizerr.New(bizerr.ErrCodeSvcBilling, "billing unavailable"),
			expectedErrCode: bizerr.ErrCodeSvcBilling,
			expectedErrMsg:  "billing unavailable",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			billingCli := mockbilling.NewMockClient(ctrl)
			service := &serviceImpl{billingCli: billingCli}
			resourcePool := &globalngv1.ResourcePool{
				OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
				OwnerId:     42,
				ServicePlan: globalcommonv1.ServicePlan_BYOC,
			}
			cloudRegion := &entity.Region{Region: &globalcommonv1.Region{
				Id:              globalcommonv1.Region_AWS_US_EAST_1,
				CloudProviderId: globalcommonv1.CloudProvider_AWS,
			}}

			billingCli.EXPECT().BillingActionCheck(
				gomock.Any(),
				billing.Action_Create,
				uint64(42),
				&billing.Resource{
					Type:     billing.ResourceType_NextgenCluster,
					Region:   lo.ToPtr(globalcommonv1.Region_AWS_US_EAST_1.String()),
					Provider: lo.ToPtr(billing.Provider_AWS),
					IsFree:   false,
				},
			).Return(tt.result, tt.billingErr)

			err := service.checkTenantBillingPlan(t.Context(), resourcePool, cloudRegion)
			if tt.expectedErrCode == 0 {
				require.NoError(t, err)
				return
			}
			require.Error(t, err)
			require.Equal(t, tt.expectedErrCode, err.ErrCode())
			require.Equal(t, tt.expectedErrMsg, err.ErrMsg())
		})
	}
}

func TestCheckTenantBillingPlanSkipsNonBillableResourcePools(t *testing.T) {
	tests := []struct {
		name         string
		resourcePool *globalngv1.ResourcePool
	}{
		{
			name: "shared resource pool",
			resourcePool: &globalngv1.ResourcePool{
				OwnerType:   globalngv1.ResourcePool_SHARED,
				ServicePlan: globalcommonv1.ServicePlan_Essential_V2,
			},
		},
		{
			name: "premium resource pool",
			resourcePool: &globalngv1.ResourcePool{
				OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
				OwnerId:     42,
				ServicePlan: globalcommonv1.ServicePlan_Premium,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			service := &serviceImpl{}
			err := service.checkTenantBillingPlan(t.Context(), tt.resourcePool, &entity.Region{})
			require.NoError(t, err)
		})
	}
}

func TestCheckTenantBillingPlanRejectsUnsupportedProvider(t *testing.T) {
	ctrl := gomock.NewController(t)
	billingCli := mockbilling.NewMockClient(ctrl)
	service := &serviceImpl{billingCli: billingCli}
	resourcePool := &globalngv1.ResourcePool{
		OwnerType:   globalngv1.ResourcePool_ORGANIZATION,
		OwnerId:     42,
		ServicePlan: globalcommonv1.ServicePlan_BYOC,
	}
	cloudRegion := &entity.Region{Region: &globalcommonv1.Region{
		Id:              globalcommonv1.Region_GCP_US_CENTRAL1,
		CloudProviderId: globalcommonv1.CloudProvider_GCP,
	}}

	err := service.checkTenantBillingPlan(t.Context(), resourcePool, cloudRegion)

	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeParams, err.ErrCode())
	require.Equal(t, "unsupported cloud provider gcp for billing", err.ErrMsg())
}

func TestCreateResourcePoolValidateOnlyRejectsBillingPlanBeforeProvisioning(t *testing.T) {
	ctrl := gomock.NewController(t)
	regionRepo := mockregion.NewMockRepo(ctrl)
	billingCli := mockbilling.NewMockClient(ctrl)
	service := &serviceImpl{
		regionRepo: regionRepo,
		billingCli: billingCli,
	}
	request := bootstrapTestCreateRequest()
	request.ValidateOnly = true
	request.ResourcePool.ProviderId = globalcommonv1.CloudProvider_AWS
	request.ResourcePool.RegionId = globalcommonv1.Region_AWS_US_EAST_1
	request.ResourcePool.ServicePlan = globalcommonv1.ServicePlan_BYOC

	cloudRegion := &entity.Region{Region: &globalcommonv1.Region{
		Id:              globalcommonv1.Region_AWS_US_EAST_1,
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		ServicePlan:     globalcommonv1.ServicePlan_BYOC,
	}}
	regionRepo.EXPECT().SelectRegion(gomock.Any(), gomock.Any()).Return(cloudRegion, nil)
	billingCli.EXPECT().BillingActionCheck(
		gomock.Any(),
		billing.Action_Create,
		uint64(42),
		gomock.Any(),
	).Return(&billing.ActionCheckResult{Reason: lo.ToPtr("account has no credit")}, nil)

	response, err := service.CreateResourcePool(t.Context(), request)

	require.Nil(t, response)
	require.Error(t, err)
	require.Equal(t, bizerr.ErrCodeParams, err.ErrCode())
	require.Equal(t, "account has no credit", err.ErrMsg())
}

type allowBillingClient struct {
	billing.Client
}

func (c *allowBillingClient) BillingActionCheck(
	context.Context,
	billing.Action,
	uint64,
	*billing.Resource,
) (*billing.ActionCheckResult, bizerr.BizError) {
	return &billing.ActionCheckResult{Pass: true}, nil
}
