package cluster

import (
	"context"
	"fmt"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	tidbmgmtservicev1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/apimiddleware/bizerror"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (c *serverImpl) CleanProjectResource(
	ctx context.Context,
	request *tidbmgmtservicev1.CleanProjectResourceRequest,
) (*emptypb.Empty, error) {
	if request.ProjectId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "project id is required")
	}

	tilog.Info(ctx, "clean project resource", zap.Uint64("project id", request.ProjectId))

	// check cluster exist
	dedicatedCountResp, err := c.countDedicatedClusters(ctx, &tidbmgmtservicev1.CountClustersRequest{
		Filter: &tidbmgmtservicev1.ClusterFilter{
			ProjectIds: []uint64{request.ProjectId},
			Deleted:    lo.ToPtr(false),
		},
	})

	if err != nil {
		tilog.Error(
			ctx,
			"check dedicated cluster exist error",
			zap.Uint64("project id", request.ProjectId),
			zap.Error(err),
		)
		return nil, bizerror.NewInternal(err, "check dedicated cluster exist error")
	}

	if dedicatedCountResp.GetCount() > 0 {
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"dedicated cluster exist in project: projectId=%d",
			request.ProjectId,
		)
	}

	// check serverless cluster exist
	serverlessCountResp, err := c.countServerlessClusters(ctx, &tidbmgmtservicev1.CountClustersRequest{
		Filter: &tidbmgmtservicev1.ClusterFilter{
			ProjectIds: []uint64{request.ProjectId},
			Deleted:    lo.ToPtr(false),
		},
	})

	if err != nil {
		tilog.Error(
			ctx,
			"check serverless cluster exist error",
			zap.Uint64("project id", request.ProjectId),
			zap.Error(err),
		)
		return nil, bizerror.NewInternal(err, "check serverless cluster exist error")
	}

	if serverlessCountResp.GetCount() > 0 {
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"serverless cluster exist in project: projectId=%d",
			request.ProjectId,
		)
	}

	// delete vpc peer
	vpcPeerings, err := c.dedicatedCli.ListVpcPeerings(ctx, &globaldedicatedv1.ListVpcPeeringsRequest{
		ProjectIds: []uint64{request.ProjectId},
	})
	if err != nil {
		tilog.Error(ctx, "list vpc peerings error", zap.Uint64("project id", request.ProjectId), zap.Error(err))
		return nil, err
	}

	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		DefaultProjectID: request.GetProjectId(),
	})

	for _, vpcPeering := range vpcPeerings.VpcPeerings {
		if vpcPeering.ProjectId != request.ProjectId {
			err := fmt.Errorf("vpc peering not belong to this project")
			tilog.Error(
				ctx,
				err.Error(),
				zap.Any("vpc peering", vpcPeering),
				zap.Uint64("project id", request.ProjectId),
			)
			return nil, bizerror.NewInternal(err, err.Error())
		}

		_, err := c.dedicatedCli.DeleteVpcPeering(ctx, &globaldedicatedv1.DeleteVpcPeeringRequest{
			VpcPeeringId:  vpcPeering.Id,
			CloudProvider: vpcPeering.CloudProvider,
		})
		if err != nil {
			tilog.Error(
				ctx,
				"delete vpc peering error",
				zap.Uint64("project id", request.ProjectId),
				zap.Uint64("vpc peering id", vpcPeering.Id),
				zap.Error(err),
			)
			return nil, err
		}
	}

	// delete vpcs
	vpcs, err := c.dedicatedCli.ListVpcs(ctx, &globaldedicatedv1.ListVpcsRequest{ProjectId: request.ProjectId})
	if err != nil {
		tilog.Error(ctx, "list vpcs error", zap.Uint64("project id", request.ProjectId), zap.Error(err))
		return nil, err
	}

	for _, vpc := range vpcs.Vpcs {
		if vpc.ProjectId != request.ProjectId {
			err := fmt.Errorf("vpc not belong to this project")
			tilog.Error(ctx, err.Error(), zap.Any("vpc", vpc), zap.Uint64("project id", request.ProjectId))
			return nil, bizerror.NewInternal(err, err.Error())
		}
		_, err := c.dedicatedCli.DeleteVpc(ctx, &globaldedicatedv1.DeleteVpcRequest{
			VpcId:           vpc.Id,
			RegionId:        vpc.RegionId,
			CloudProviderId: vpc.CloudProviderId,
			ProjectId:       vpc.ProjectId,
		})
		if err != nil {
			tilog.Error(ctx, "delete vpc error", zap.String("vpc id", vpc.Id), zap.Error(err))
			return nil, err
		}
	}

	// delete cmek
	cmeks, err := c.dedicatedCli.ListCustomerManagedEncryptionKeys(ctx,
		&globaldedicatedv1.ListCustomerManagedEncryptionKeysRequest{ProjectIds: []uint64{request.ProjectId}})
	if err != nil {
		tilog.Error(ctx, "list cmeks error", zap.Uint64("project id", request.ProjectId), zap.Error(err))
		return nil, err
	}

	for _, cmek := range cmeks.CustomerManagedEncryptionKeys {
		if cmek.ProjectId != request.ProjectId {
			err := fmt.Errorf("cmek not belong to this project")
			tilog.Error(ctx, err.Error(), zap.Any("cmek", cmek), zap.Uint64("project id", request.ProjectId))
			return nil, bizerror.NewInternal(err, err.Error())
		}

		_, err := c.dedicatedCli.DeleteCustomerManagedEncryptionKey(
			ctx,
			&globaldedicatedv1.DeleteCustomerManagedEncryptionKeyRequest{CustomerManagedEncryptionKeyId: cmek.Id},
		)
		if err != nil {
			tilog.Error(
				ctx,
				"delete cmek error",
				zap.Uint64("project id", request.ProjectId),
				zap.Uint64("cmek id", cmek.Id),
				zap.Error(err),
			)
			return nil, err
		}
	}

	return &emptypb.Empty{}, nil
}

func (c *serverImpl) CleanOrganizationResource(
	ctx context.Context,
	request *tidbmgmtservicev1.CleanOrganizationResourceRequest,
) (*emptypb.Empty, error) {
	if request.OrganizationId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "organization id is required")
	}

	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		OrgID: request.OrganizationId,
	})

	tilog.Info(ctx, "clean organization resource", zap.Uint64("organization id", request.OrganizationId))

	// check cluster exist
	dedicatedCountResp, err := c.countDedicatedClusters(ctx, &tidbmgmtservicev1.CountClustersRequest{
		Filter: &tidbmgmtservicev1.ClusterFilter{
			TenantIds: []uint64{request.OrganizationId},
			Deleted:   lo.ToPtr(false),
		},
	})

	if err != nil {
		tilog.Error(
			ctx,
			"check dedicated cluster exist error",
			zap.Uint64("organization id", request.OrganizationId),
			zap.Error(err),
		)
		return nil, bizerror.NewInternal(err, "check dedicated cluster exist error")
	}

	if dedicatedCountResp.GetCount() > 0 {
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"dedicated cluster exist in organization: organizationId=%d",
			request.OrganizationId,
		)
	}

	// check serverless cluster exist
	serverlessCountResp, err := c.countServerlessClusters(ctx, &tidbmgmtservicev1.CountClustersRequest{
		Filter: &tidbmgmtservicev1.ClusterFilter{
			TenantIds: []uint64{request.OrganizationId},
			Deleted:   lo.ToPtr(false),
		},
	})

	if err != nil {
		tilog.Error(
			ctx,
			"check serverless cluster exist error",
			zap.Uint64("organization id", request.OrganizationId),
			zap.Error(err),
		)
		return nil, bizerror.NewInternal(err, "check serverless cluster exist error")
	}

	if serverlessCountResp.GetCount() > 0 {
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"serverless cluster exist in organization: organizationId=%d",
			request.OrganizationId,
		)
	}

	// check nextgen cluster exist
	nextgenCountResp, err := c.countNextgenClusters(ctx, &tidbmgmtservicev1.CountClustersRequest{
		Filter: &tidbmgmtservicev1.ClusterFilter{
			TenantIds: []uint64{request.OrganizationId},
			Deleted:   lo.ToPtr(false),
		},
	})
	if err != nil {
		tilog.Error(
			ctx,
			"check nextgen cluster exist error",
			zap.Uint64("organization id", request.OrganizationId),
			zap.Error(err),
		)
		return nil, bizerror.NewInternal(err, "check nextgen cluster exist error")
	}
	if nextgenCountResp.GetCount() > 0 {
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"nextgen cluster exist in organization: organizationId=%d",
			request.OrganizationId,
		)
	}

	// delete vpc peer
	vpcPeerings, err := c.dedicatedCli.ListVpcPeerings(ctx, &globaldedicatedv1.ListVpcPeeringsRequest{
		OrganizationId: request.OrganizationId,
	})
	if err != nil {
		tilog.Error(
			ctx,
			"list vpc peerings error",
			zap.Uint64("organization id", request.OrganizationId),
			zap.Error(err),
		)
		return nil, err
	}

	for _, vpcPeering := range vpcPeerings.VpcPeerings {
		if vpcPeering.OrganizationId != request.OrganizationId {
			err := fmt.Errorf("vpc peering not belong to this organization")
			tilog.Error(
				ctx,
				err.Error(),
				zap.Any("vpc", vpcPeering),
				zap.Uint64("organization id", request.OrganizationId),
			)
			return nil, bizerror.NewInternal(err, err.Error())
		}
		_, err := c.dedicatedCli.DeleteVpcPeering(ctx, &globaldedicatedv1.DeleteVpcPeeringRequest{
			VpcPeeringId:  vpcPeering.Id,
			CloudProvider: vpcPeering.CloudProvider,
		})
		if err != nil {
			tilog.Error(
				ctx,
				"delete vpc peering error",
				zap.Uint64("organization id", request.OrganizationId),
				zap.Uint64("vpc peering id", vpcPeering.Id),
				zap.Error(err),
			)
			return nil, err
		}
	}

	// delete vpc
	vpcs, err := c.dedicatedCli.ListVpcs(
		ctx,
		&globaldedicatedv1.ListVpcsRequest{OrganizationId: request.OrganizationId},
	)
	if err != nil {
		tilog.Error(ctx, "list vpcs error", zap.Uint64("organization id", request.OrganizationId), zap.Error(err))
		return nil, err
	}

	for _, vpc := range vpcs.Vpcs {
		if vpc.OrganizationId != request.OrganizationId {
			err := fmt.Errorf("vpc not belong to this organization")
			tilog.Error(ctx, err.Error(), zap.Any("vcp", vpc), zap.Uint64("organization id", request.OrganizationId))
			return nil, bizerror.NewInternal(err, err.Error())
		}
		_, err := c.dedicatedCli.DeleteVpc(ctx, &globaldedicatedv1.DeleteVpcRequest{
			VpcId:           vpc.Id,
			RegionId:        vpc.RegionId,
			CloudProviderId: vpc.CloudProviderId,
			OrganizationId:  vpc.OrganizationId,
		})
		if err != nil {
			tilog.Error(ctx, "delete vpc error", zap.String("vpc id", vpc.Id), zap.Error(err))
			return nil, err
		}
	}

	// delete cmek
	cmeks, err := c.dedicatedCli.ListCustomerManagedEncryptionKeys(ctx,
		&globaldedicatedv1.ListCustomerManagedEncryptionKeysRequest{OrganizationId: request.OrganizationId})
	if err != nil {
		tilog.Error(ctx, "list cmeks error", zap.Uint64("organization id", request.OrganizationId), zap.Error(err))
		return nil, err
	}

	for _, cmek := range cmeks.CustomerManagedEncryptionKeys {
		if cmek.OrganizationId != request.OrganizationId {
			err := fmt.Errorf("cmek not belong to this organization")
			tilog.Error(ctx, err.Error(), zap.Any("cmek", cmek), zap.Uint64("organization id", request.OrganizationId))
			return nil, bizerror.NewInternal(err, err.Error())
		}
		_, err := c.dedicatedCli.DeleteCustomerManagedEncryptionKey(
			ctx,
			&globaldedicatedv1.DeleteCustomerManagedEncryptionKeyRequest{CustomerManagedEncryptionKeyId: cmek.Id},
		)
		if err != nil {
			tilog.Error(
				ctx,
				"delete cmek error",
				zap.Uint64("organization id", request.OrganizationId),
				zap.Uint64("cmek id", cmek.Id),
				zap.Error(err),
			)
			return nil, err
		}
	}

	return &emptypb.Empty{}, nil
}

func (c *serverImpl) CleanClusters(
	ctx context.Context,
	request *tidbmgmtservicev1.CleanClustersRequest,
) (*emptypb.Empty, error) {
	if request.OrganizationId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "organization id is required")
	}

	ctx = metadata.AppendToOutgoingMeta(ctx, &metadata.RequestMeta{
		OrgID: request.OrganizationId,
	})

	switch request.DeployType {
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_UNSPECIFIED:
		if err := c.serverlessClusterSvc.CleanClusters(ctx, request.OrganizationId); err != nil {
			return nil, bizerror.NewInternal(err, "failed to clean serverless cluster")
		}
		if err := c.dedicatedClusterSvc.CleanClusters(ctx, request.OrganizationId, !request.DisableFinalBackup); err != nil {
			return nil, bizerror.NewInternal(err, "failed to clean dedicated cluster")
		}
		if err := c.nextgenClusterSvc.CleanClusters(ctx, request.OrganizationId, !request.DisableFinalBackup); err != nil {
			return nil, bizerror.NewInternal(err, "failed to clean nextgen cluster")
		}
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_DEDICATED:
		if err := c.dedicatedClusterSvc.CleanClusters(ctx, request.OrganizationId, !request.DisableFinalBackup); err != nil {
			return nil, bizerror.NewInternal(err, "failed to clean dedicated cluster")
		}
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_SERVERLESS:
		if err := c.serverlessClusterSvc.CleanClusters(ctx, request.OrganizationId); err != nil {
			return nil, bizerror.NewInternal(err, "failed to clean serverless cluster")
		}
	case tidbmgmtservicev1.DeployType_DEPLOY_TYPE_NEXTGEN:
		if err := c.nextgenClusterSvc.CleanClusters(ctx, request.OrganizationId, !request.DisableFinalBackup); err != nil {
			return nil, bizerror.NewInternal(err, "failed to clean nextgen cluster")
		}
	}

	return &emptypb.Empty{}, nil
}
