package region

import (
	"context"
	"testing"

	. "github.com/onsi/gomega"

	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/entity"
	regionrepoimpl "github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/region"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/idgenerator"
)

func TestRegisterCloudProvider(t *testing.T) {
	tests := []struct {
		name    string
		req     *globalngv1.RegisterCloudProviderRequest
		want    *entity.CloudProvider
		wantErr bool
		ErrCode bizerr.ErrCode
	}{
		{
			name: "register cloud provider",
			req: &globalngv1.RegisterCloudProviderRequest{
				CloudProvider: &globalcommonv1.CloudProvider{
					Id:          globalcommonv1.CloudProvider_AZURE,
					ServicePlan: globalcommonv1.ServicePlan_Premium,
					DisplayName: "Azure",
					IsPublic:    true,
					State:       globalcommonv1.CloudProvider_ACTIVE,
				},
			},
			want: &entity.CloudProvider{
				CloudProvider: &globalcommonv1.CloudProvider{
					Id:          globalcommonv1.CloudProvider_AZURE,
					ServicePlan: globalcommonv1.ServicePlan_Premium,
					DisplayName: "Azure",
					IsPublic:    true,
					State:       globalcommonv1.CloudProvider_ACTIVE,
				},
			},
		},
		{
			name: "duplicate cloud provider",
			req: &globalngv1.RegisterCloudProviderRequest{
				CloudProvider: &globalcommonv1.CloudProvider{
					Id:          globalcommonv1.CloudProvider_AZURE,
					ServicePlan: globalcommonv1.ServicePlan_Premium,
					DisplayName: "Azure",
					State:       globalcommonv1.CloudProvider_ACTIVE,
				},
			},
			wantErr: true,
			ErrCode: bizerr.ErrCodeRecordExist,
		},
		{
			name: "invalid cloud provider",
			req: &globalngv1.RegisterCloudProviderRequest{
				CloudProvider: &globalcommonv1.CloudProvider{
					Id: globalcommonv1.CloudProvider_NAME_UNSPECIFIED,
				},
			},
			wantErr: true,
			ErrCode: bizerr.ErrCodeParams,
		},
		{
			name: "register cloud provider without service plan",
			req: &globalngv1.RegisterCloudProviderRequest{
				CloudProvider: &globalcommonv1.CloudProvider{
					Id:          globalcommonv1.CloudProvider_GCP,
					DisplayName: "GCP",
					IsPublic:    true,
					State:       globalcommonv1.CloudProvider_ACTIVE,
				},
			},
			want: &entity.CloudProvider{
				CloudProvider: &globalcommonv1.CloudProvider{
					Id:          globalcommonv1.CloudProvider_GCP,
					ServicePlan: globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED,
					DisplayName: "GCP",
					IsPublic:    true,
					State:       globalcommonv1.CloudProvider_ACTIVE,
				},
			},
		},
		{
			name: "display name is required",
			req: &globalngv1.RegisterCloudProviderRequest{
				CloudProvider: &globalcommonv1.CloudProvider{
					Id: globalcommonv1.CloudProvider_AZURE,
				},
			},
			wantErr: true,
			ErrCode: bizerr.ErrCodeParams,
		},
	}

	dbPvd := *dbProvider.Load()
	regionRepo := regionrepoimpl.NewRegionRepo(dbPvd)
	providerIdentityRepo := regionrepoimpl.NewRegionProviderIdentityRepo(dbPvd)
	idGeneratorAdapter := idgenerator.NewSonyflakeID(1, "")

	cleanupRegisteredProviders := func() error {
		return dbPvd.WriteDB(context.Background()).
			Unscoped().
			Where(
				"(`providers`.`name` = ? AND `providers`.`service_plan` = ?) OR (`providers`.`name` = ? AND `providers`.`service_plan` = ?)",
				globalcommonv1.CloudProvider_AZURE.String(), globalcommonv1.ServicePlan_Premium.String(),
				globalcommonv1.CloudProvider_GCP.String(), globalcommonv1.ServicePlan_Premium.String(),
			).
			Delete(&schema.Provider{}).
			Error
	}
	NewWithT(t).Expect(cleanupRegisteredProviders()).ShouldNot(HaveOccurred())
	t.Cleanup(func() {
		NewWithT(t).Expect(cleanupRegisteredProviders()).ShouldNot(HaveOccurred())
	})

	svc := NewService(regionRepo, nil,
		providerIdentityRepo, nil,
		nil, dbPvd, nil,
		idGeneratorAdapter, nil, nil)
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			g := NewWithT(t)

			cloudProvider, err := svc.RegisterCloudProvider(t.Context(), test.req)
			if test.wantErr {
				g.Expect(err).Should(HaveOccurred())
				bizErr, ok := err.(*bizerr.BizErr)
				g.Expect(ok).To(BeTrue())
				g.Expect(bizErr.ErrCode()).To(Equal(test.ErrCode))
			} else {
				g.Expect(err).Should(BeNil())
				g.Expect(cloudProvider.ProviderID).ShouldNot(BeZero())
				g.Expect(cloudProvider.CloudProvider.Id).Should(Equal(test.want.Id))
				g.Expect(cloudProvider.CloudProvider.ServicePlan).Should(Equal(test.want.ServicePlan))
				g.Expect(cloudProvider.CloudProvider.DisplayName).Should(Equal(test.want.DisplayName))
				g.Expect(cloudProvider.CloudProvider.State).Should(Equal(test.want.State))

				var provider schema.Provider
				dbPvd.ReadDB(t.Context()).Model(&schema.Provider{}).Where("`providers`.`id` = ?", cloudProvider.ProviderID).Take(&provider)
				g.Expect(provider.Name).Should(Equal(test.want.Id.String()))
				if test.want.ServicePlan != globalcommonv1.ServicePlan_SERVICE_PLAN_UNSPECIFIED {
					g.Expect(provider.ServicePlan).Should(Equal(test.want.ServicePlan.String()))
				}
				g.Expect(provider.DisplayName).Should(Equal(test.want.DisplayName))
				g.Expect(provider.Status).Should(Equal(test.want.State.String()))
			}
		})
	}
}

func TestListCloudProviders(t *testing.T) {
	tests := []struct {
		name    string
		req     *globalngv1.ListCloudProvidersRequest
		want    []*entity.CloudProvider
		wantErr bool
		ErrCode bizerr.ErrCode
	}{
		{
			// With decoupling, providers table has one row per (vendor, plan).
			// ServicePlan=BYOC filter returns providers with service_plan=BYOC, ordered by id ASC.
			name: "list cloud providers",
			req: &globalngv1.ListCloudProvidersRequest{
				ServicePlan: globalcommonv1.ServicePlan_BYOC,
				State:       globalcommonv1.CloudProvider_ACTIVE,
			},
			want: []*entity.CloudProvider{
				{
					CloudProvider: &globalcommonv1.CloudProvider{
						Id:          globalcommonv1.CloudProvider_AZURE,
						ServicePlan: globalcommonv1.ServicePlan_BYOC,
						DisplayName: "Azure",
						IsPublic:    true,
						State:       globalcommonv1.CloudProvider_ACTIVE,
					},
					ProviderID: 4,
				},
				{
					CloudProvider: &globalcommonv1.CloudProvider{
						Id:          globalcommonv1.CloudProvider_AWS,
						ServicePlan: globalcommonv1.ServicePlan_BYOC,
						DisplayName: "AWS",
						IsPublic:    true,
						State:       globalcommonv1.CloudProvider_ACTIVE,
					},
					ProviderID: 5,
				},
			},
			wantErr: false,
		},
		{
			// Without a service plan filter, active providers are deduped by name and ordered by the
			// first/lowest provider id for each vendor from the package test fixture.
			name: "list cloud provider without service plan",
			req: &globalngv1.ListCloudProvidersRequest{
				State: globalcommonv1.CloudProvider_ACTIVE,
			},
			wantErr: false,
			want: []*entity.CloudProvider{
				{
					CloudProvider: &globalcommonv1.CloudProvider{
						Id:          globalcommonv1.CloudProvider_AWS,
						ServicePlan: globalcommonv1.ServicePlan_Premium,
						DisplayName: "AWS",
						IsPublic:    true,
						State:       globalcommonv1.CloudProvider_ACTIVE,
					},
					ProviderID: 1,
				},
				{
					CloudProvider: &globalcommonv1.CloudProvider{
						Id:          globalcommonv1.CloudProvider_AZURE,
						ServicePlan: globalcommonv1.ServicePlan_BYOC,
						DisplayName: "Azure",
						IsPublic:    true,
						State:       globalcommonv1.CloudProvider_ACTIVE,
					},
					ProviderID: 4,
				},
			},
		},
		{
			// With decoupling, ServicePlan=BYOC filter returns providers with service_plan=BYOC from the
			// providers table (ordered by id ASC). No state filter means inactive providers are included.
			name: "list cloud provider without state",
			req: &globalngv1.ListCloudProvidersRequest{
				ServicePlan: globalcommonv1.ServicePlan_BYOC,
			},
			wantErr: false,
			want: []*entity.CloudProvider{
				{
					CloudProvider: &globalcommonv1.CloudProvider{
						Id:          globalcommonv1.CloudProvider_GCP,
						ServicePlan: globalcommonv1.ServicePlan_BYOC,
						DisplayName: "GCP",
						IsPublic:    true,
						State:       globalcommonv1.CloudProvider_INACTIVE,
					},
					ProviderID: 3,
				},
				{
					CloudProvider: &globalcommonv1.CloudProvider{
						Id:          globalcommonv1.CloudProvider_AZURE,
						ServicePlan: globalcommonv1.ServicePlan_BYOC,
						DisplayName: "Azure",
						IsPublic:    true,
						State:       globalcommonv1.CloudProvider_ACTIVE,
					},
					ProviderID: 4,
				},
				{
					CloudProvider: &globalcommonv1.CloudProvider{
						Id:          globalcommonv1.CloudProvider_AWS,
						ServicePlan: globalcommonv1.ServicePlan_BYOC,
						DisplayName: "AWS",
						IsPublic:    true,
						State:       globalcommonv1.CloudProvider_ACTIVE,
					},
					ProviderID: 5,
				},
			},
		},
		{
			name: "list essential v2 cloud provider",
			req: &globalngv1.ListCloudProvidersRequest{
				ServicePlan: globalcommonv1.ServicePlan_Essential_V2,
				State:       globalcommonv1.CloudProvider_ACTIVE,
			},
			wantErr: false,
			want: []*entity.CloudProvider{
				{
					CloudProvider: &globalcommonv1.CloudProvider{
						Id:          globalcommonv1.CloudProvider_AWS,
						ServicePlan: globalcommonv1.ServicePlan_Essential_V2,
						DisplayName: "AWS",
						IsPublic:    true,
						State:       globalcommonv1.CloudProvider_ACTIVE,
					},
					ProviderID: 6,
				},
			},
		},
	}

	dbPvd := *dbProvider.Load()
	regionRepo := regionrepoimpl.NewRegionRepo(dbPvd)
	providerIdentityRepo := regionrepoimpl.NewRegionProviderIdentityRepo(dbPvd)
	idGeneratorAdapter := idgenerator.NewSonyflakeID(1, "")

	svc := NewService(regionRepo, nil, providerIdentityRepo, nil, nil, dbPvd, nil, idGeneratorAdapter, nil, nil)

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			g := NewWithT(t)

			cloudProviders, err := svc.ListCloudProviders(t.Context(), test.req)
			if test.wantErr {
				g.Expect(err).Should(HaveOccurred())
				bizErr, ok := err.(*bizerr.BizErr)
				g.Expect(ok).To(BeTrue())
				g.Expect(bizErr.ErrCode()).To(Equal(test.ErrCode))
			} else {
				g.Expect(err).Should(BeNil())
				g.Expect(len(cloudProviders)).Should(Equal(len(test.want)))
				for i, each := range cloudProviders {
					g.Expect(each.CloudProvider.Id).Should(Equal(test.want[i].Id))
					g.Expect(each.CloudProvider.ServicePlan).Should(Equal(test.want[i].ServicePlan))
					g.Expect(each.CloudProvider.DisplayName).Should(Equal(test.want[i].DisplayName))
					g.Expect(each.CloudProvider.State).Should(Equal(test.want[i].State))
				}
			}
		})
	}
}
