package cluster

import (
	"context"
	"testing"

	"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/eventbus/mock_eventbus"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	auditlogrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/auditlog"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/br/mock_br"
	repobyocvpcpeering "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/byocvpcpeering"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster/mock_cluster"
	bindingrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/clusterencryptionbinding"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/clusterencryptionbinding/mock_clusterencryptionbinding"
	exportrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/export"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/export/mock_export"
	logredactionpolicyrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/logredactionpolicy"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/privatelink"
	publicconnectionsettingrepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/publicconnectionsetting"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool/mock_resourcepool"
	tidbgrouprepo "github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/tidbgroup"
	"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/clusterevent"
	"github.com/tidbcloud/tidb-management-service/utilities/workflow/mock_workflow"
)

func TestDeleteClusterDeletesByocVpcPeeringAllowlistSetting(t *testing.T) {
	ctx := t.Context()
	ctrl := gomock.NewController(t)

	clusterRepo := mock_cluster.NewMockRepo(ctrl)
	exportRepo := mock_export.NewMockExportRepo(ctrl)
	eventCommitter := mock_eventbus.NewMockWALCommitter(ctrl)
	backupSettingRepo := mock_br.NewMockBackupSettingRepo(ctrl)
	bindingRepo := mock_clusterencryptionbinding.NewMockRepo(ctrl)
	workflow := mock_workflow.NewMockWorkflow(ctrl)
	resourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)

	tidbgroupRepo := &fakeDeleteTidbGroupRepo{}
	publicSettingRepo := &fakeDeletePublicConnectionSettingRepo{}
	byocAllowlistRepo := &fakeDeleteByocVpcPeeringRepo{}
	privateLinkRepo := &fakeDeletePrivateLinkRepo{}
	auditLogSettingRepo := &fakeDeleteAuditLogSettingRepo{}
	auditLogFilterRuleRepo := &fakeDeleteAuditLogFilterRuleRepo{}
	logRedactionPolicyRepo := &fakeDeleteLogRedactionPolicyRepo{}

	svc := &serviceImpl{
		workflow:                    workflow,
		transaction:                 fakeTransactioner{},
		clusterRepo:                 clusterRepo,
		tidbgroupRepo:               tidbgroupRepo,
		publicConnectionSettingRepo: publicSettingRepo,
		byocVpcPeeringRepo:          byocAllowlistRepo,
		privatelinkRepo:             privateLinkRepo,
		auditLogSettingRepo:         auditLogSettingRepo,
		auditLogFilterRuleRepo:      auditLogFilterRuleRepo,
		logRedactionPolicyRepo:      logRedactionPolicyRepo,
		backupSettingRepo:           backupSettingRepo,
		bindingRepo:                 bindingRepo,
		exportRepo:                  exportRepo,
		eventCommitter:              eventCommitter,
		resourcePoolRepo:            resourcePoolRepo,
	}

	cluster := &entity.Cluster{
		Cluster: &globalngv1.Cluster{
			ClusterId:       101,
			OrganizationId:  202,
			State:           globalngv1.Cluster_AVAILABLE,
			ServicePlan:     globalcommonv1.ServicePlan_Starter,
			CloudProviderId: globalcommonv1.CloudProvider_AWS,
			SharedPoolId:    "303",
		},
	}
	resourcePool := &entity.ResourcePool{ResourcePool: &globalngv1.ResourcePool{
		Id:             303,
		LifecycleState: globalngv1.ResourcePool_ACTIVE,
	}}

	clusterRepo.EXPECT().Get(gomock.Any(), uint64(101), false).Return(cluster, nil)
	resourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(303), false).Return(resourcePool, nil)
	resourcePoolRepo.EXPECT().LoadForUpdate(gomock.Any(), uint64(303), false).Return(resourcePool, nil)
	exportRepo.EXPECT().Count(gomock.Any(), &exportrepo.QueryExportsFilter{
		ClusterIds: []string{"101"},
		States:     []schema.ExportState{schema.ExportStateRunning},
	}).Return(int64(0), nil)
	eventCommitter.EXPECT().BatchWrite(gomock.Any(), clusterevent.TopicClusterEvent, gomock.Any()).Return(nil)
	clusterRepo.EXPECT().Save(gomock.Any(), gomock.Any()).Return(nil)
	clusterRepo.EXPECT().Delete(gomock.Any(), gomock.Any()).Return(nil)
	backupSettingRepo.EXPECT().Delete(gomock.Any(), uint64(101)).Return(bizerr.New(bizerr.ErrCodeRecordNotFound, "not found"))
	bindingRepo.EXPECT().Delete(gomock.Any(), uint64(101)).Return(nil)
	workflow.EXPECT().DeleteDagInstances(gomock.Any(), "101", map[string]bool{"delete_tidb_group": true}).Return(nil)
	workflow.EXPECT().RunDagWithBizId(gomock.Any(), "delete_cluster", "202", "0", "101", gomock.Any()).Return(nil)

	resp, err := svc.Delete(ctx, &globalngv1.DeleteClusterRequest{ClusterId: 101})

	require.NoError(t, err)
	require.NotNil(t, resp)
	require.True(t, byocAllowlistRepo.deleteCalled)
	require.Equal(t, uint64(101), byocAllowlistRepo.deletedClusterID)
}

type fakeTransactioner struct{}

func (fakeTransactioner) Transaction(ctx context.Context, cb func(ctx context.Context) error) error {
	return cb(ctx)
}

type fakeDeleteTidbGroupRepo struct{}

func (f *fakeDeleteTidbGroupRepo) Create(context.Context, *entity.TidbGroup) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeleteTidbGroupRepo) Save(context.Context, *entity.TidbGroup) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeleteTidbGroupRepo) Load(context.Context, uint64, uint64) (*entity.TidbGroup, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeleteTidbGroupRepo) Delete(context.Context, *entity.TidbGroup) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeleteTidbGroupRepo) DeleteByClusterId(context.Context, uint64) bizerr.BizError {
	return nil
}

func (f *fakeDeleteTidbGroupRepo) Query(context.Context, *tidbgrouprepo.Filter) ([]*entity.TidbGroup, bizerr.BizError) {
	return []*entity.TidbGroup{}, nil
}

type fakeDeletePublicConnectionSettingRepo struct{}

func (f *fakeDeletePublicConnectionSettingRepo) Save(context.Context, *entity.PublicConnectionSetting) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeletePublicConnectionSettingRepo) LoadByClusterId(context.Context, uint64) ([]*entity.PublicConnectionSetting, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeletePublicConnectionSettingRepo) GetByTidbGroupId(context.Context, uint64) (*entity.PublicConnectionSetting, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeletePublicConnectionSettingRepo) DeleteByClusterID(context.Context, uint64) bizerr.BizError {
	return nil
}

func (f *fakeDeletePublicConnectionSettingRepo) DeleteByTidbGroupId(context.Context, uint64) bizerr.BizError {
	panic("unexpected call")
}

type fakeDeleteByocVpcPeeringRepo struct {
	deleteCalled     bool
	deletedClusterID uint64
}

func (f *fakeDeleteByocVpcPeeringRepo) GetByClusterID(context.Context, uint64) (*entity.VpcPeeringAllowlistSetting, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeleteByocVpcPeeringRepo) Save(context.Context, *entity.VpcPeeringAllowlistSetting) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeleteByocVpcPeeringRepo) DeleteByClusterID(_ context.Context, clusterID uint64) bizerr.BizError {
	f.deleteCalled = true
	f.deletedClusterID = clusterID
	return nil
}

type fakeDeletePrivateLinkRepo struct{}

func (f *fakeDeletePrivateLinkRepo) CreatePrivateLinkService(context.Context, *entity.PrivateLinkService) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeletePrivateLinkRepo) UpdatePrivateLinkService(context.Context, *entity.PrivateLinkService) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeletePrivateLinkRepo) ListPrivateLinkServices(context.Context, *privatelink.Filter) ([]*entity.PrivateLinkService, bizerr.BizError) {
	return []*entity.PrivateLinkService{}, nil
}

func (f *fakeDeletePrivateLinkRepo) DeletePrivateLinkService(context.Context, uint64) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeletePrivateLinkRepo) GetPrivateLinkService(context.Context, uint64) (*entity.PrivateLinkService, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeletePrivateLinkRepo) CreatePrivateLinkConnection(context.Context, *entity.PrivateLinkConnection) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeletePrivateLinkRepo) GetPrivateLinkConnection(context.Context, uint64) (*entity.PrivateLinkConnection, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeletePrivateLinkRepo) ListPrivateLinkConnections(
	context.Context,
	*privatelink.Filter,
) ([]*entity.PrivateLinkConnection, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeletePrivateLinkRepo) DeletePrivateLinkConnection(context.Context, uint64) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeletePrivateLinkRepo) DeletePrivateLinkConnectionByServiceID(context.Context, uint64) bizerr.BizError {
	panic("unexpected call")
}

type fakeDeleteAuditLogSettingRepo struct{}

func (f *fakeDeleteAuditLogSettingRepo) Query(context.Context, *auditlogrepo.SettingFilter) ([]*globalngv1.AuditLogSetting, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogSettingRepo) GetByClusterID(context.Context, uint64) (*globalngv1.AuditLogSetting, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogSettingRepo) Get(context.Context, uint64) (*globalngv1.AuditLogSetting, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogSettingRepo) Upsert(context.Context, *globalngv1.AuditLogSetting) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogSettingRepo) DeleteByClusterID(context.Context, uint64) bizerr.BizError {
	return nil
}

type fakeDeleteAuditLogFilterRuleRepo struct{}

func (f *fakeDeleteAuditLogFilterRuleRepo) Create(context.Context, *globalngv1.AuditLogFilterRule) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogFilterRuleRepo) Query(
	context.Context,
	*auditlogrepo.FilterRuleFilter,
) ([]*globalngv1.AuditLogFilterRule, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogFilterRuleRepo) GetByUserExpr(context.Context, uint64, string) (*globalngv1.AuditLogFilterRule, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogFilterRuleRepo) Get(context.Context, uint64) (*globalngv1.AuditLogFilterRule, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogFilterRuleRepo) Update(context.Context, *globalngv1.AuditLogFilterRule) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogFilterRuleRepo) Delete(context.Context, uint64) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeleteAuditLogFilterRuleRepo) DeleteByClusterID(context.Context, uint64) bizerr.BizError {
	return nil
}

type fakeDeleteLogRedactionPolicyRepo struct{}

func (f *fakeDeleteLogRedactionPolicyRepo) GetByClusterID(context.Context, uint64) (*globalngv1.LogRedactionPolicy, bizerr.BizError) {
	panic("unexpected call")
}

func (f *fakeDeleteLogRedactionPolicyRepo) Upsert(context.Context, *globalngv1.LogRedactionPolicy) bizerr.BizError {
	panic("unexpected call")
}

func (f *fakeDeleteLogRedactionPolicyRepo) DeleteByClusterID(context.Context, uint64) bizerr.BizError {
	return nil
}

var _ repobyocvpcpeering.Repo = (*fakeDeleteByocVpcPeeringRepo)(nil)
var _ publicconnectionsettingrepo.Repo = (*fakeDeletePublicConnectionSettingRepo)(nil)
var _ tidbgrouprepo.Repo = (*fakeDeleteTidbGroupRepo)(nil)
var _ privatelink.Repo = (*fakeDeletePrivateLinkRepo)(nil)
var _ auditlogrepo.SettingRepo = (*fakeDeleteAuditLogSettingRepo)(nil)
var _ auditlogrepo.FilterRuleRepo = (*fakeDeleteAuditLogFilterRuleRepo)(nil)
var _ logredactionpolicyrepo.Repo = (*fakeDeleteLogRedactionPolicyRepo)(nil)
var _ bindingrepo.Repo = (*mock_clusterencryptionbinding.MockRepo)(nil)
