//go:generate ${GOBIN}/mockgen -destination mgmt_client_mock.go -package=coresvccli github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1 ClusterServiceClient,ClusterCustomStatusServiceClient,PrivateLinkServiceMigrationServiceClient,ReservedCapacityTemplateServiceClient,DedicatedToPremiumPrecheckServiceClient,RegionServiceClient
//go:generate ${GOBIN}/mockgen -destination coresvc_client_mock.go -package=coresvccli github.com/tidbcloud/core-svc/idl/pbgen/proto/core CoreServiceClient
//go:generate ${GOBIN}/mockgen -destination resource_pool_client_mock.go -package=coresvccli github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1 ResourcePoolServiceClient
package coresvccli

import (
	"context"
	"fmt"
	"strconv"

	"github.com/tidbcloud/devops/api"
	"github.com/tidbcloud/devops/pkg/infra/errs"
	"github.com/tidbcloud/devops/pkg/infra/utils/grpcutil"

	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	commonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	nextgenv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	v1beta11 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	mgmtv1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_mgmt_service/v1"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"google.golang.org/grpc"
)

const (
	// The values of Cluster.InfraProviderType.
	// Ref: https://github.com/tidbcloud/core-svc/blob/master/portal/internal/central/infra-api/api.go#L45
	InfraProviderTypeAWS        = "aws"
	InfraProviderTypeGCP        = "gcp"
	InfraProviderTypeEKS        = "eks"
	InfraProviderTypeGKE        = "gke"
	InfraProviderTypeAKS        = "aks"
	InfraProviderTypeAWSDevTier = "aws-dev-tier"
)

type ListClustersParam struct {
	TenantIDs   []uint64
	ProjectIDs  []uint64
	ClusterIDs  []uint64
	IsDevTier   *bool                 // for core-svc api
	DeployType  *mgmtv1.DeployType    // for mgmt api
	ServicePlan *v1beta11.ServicePlan // for mgmt api

	Page    int64
	PerPage int64
}

type UpdateRegionOpsAuditLogSettingReq struct {
	RegionLogicalName string       `json:"region_logical_name"`
	Enabled           bool         `json:"enabled"`
	LogLocation       *LogLocation `json:"log_location,omitempty"`
	LogRotation       *LogRotation `json:"log_rotation,omitempty"`
}

type LogLocation struct {
	BucketUri  string `json:"bucket_uri"`
	RoleArn    string `json:"role_arn"`
	ExternalId string `json:"external_id"`
	AccountId  string `json:"account_id"`
}

type LogRotation struct {
	FileSizeLimitInMb     uint64 `json:"file_size_limit_in_mb"`
	RotationIntervalInMin uint64 `json:"rotation_interval_in_min"`
}

type MgmtService struct {
	Cluster                     mgmtv1.ClusterServiceClient
	ClusterCustomStatus         mgmtv1.ClusterCustomStatusServiceClient
	Maintenance                 mgmtv1.MaintenanceServiceClient
	Dataplane                   mgmtv1.DataPlaneServiceClient
	SpuSku                      mgmtv1.SpuServiceClient
	Vpc                         mgmtv1.VpcServiceClient
	Region                      mgmtv1.RegionServiceClient
	PrivateLinkServiceMigration mgmtv1.PrivateLinkServiceMigrationServiceClient
	ResourcePool                mgmtv1.ResourcePoolServiceClient
	ClusterMigration            mgmtv1.MigrationClusterServiceClient
	RcuQuota                    mgmtv1.RcuQuotaServiceClient
	DataSynchronization         mgmtv1.DataSynchronizationServiceClient
	ReservedCapacityTemplate    mgmtv1.ReservedCapacityTemplateServiceClient
	DedicatedToPremiumPrecheck  mgmtv1.DedicatedToPremiumPrecheckServiceClient
	NextGenExport               nextgenv1.ExportServiceClient
	NextGenBackup               nextgenv1.BackupServiceClient
	NextGenCluster              nextgenv1.ClusterServiceClient
	mgmtSvcConn                 grpc.ClientConnInterface
}

func (s *MgmtService) GetCluster(ctx context.Context, clusterID uint64) (*mgmtv1.Cluster, error) {
	req := &mgmtv1.ListClustersRequest{
		Filter: &mgmtv1.ClusterFilter{
			ClusterIds: []uint64{clusterID},
		},
	}
	resp, err := s.Cluster.ListClusters(ctx, req)
	if err != nil {
		return nil, err
	}
	if len(resp.Clusters) == 0 {
		return nil, errs.APIErrorf(api.ErrCodeNotFound, "cluster %d not found", clusterID)
	}

	return resp.Clusters[0], nil
}

// OverrideFlexibleBackupRetention temporarily invokes the internal BackupService
// without using its generated management API client. This keeps DevOps independent
// of the unreleased BackupService API module; will replace it with BackupServiceClient
// once that API is merged into main and released.
func (s *MgmtService) OverrideFlexibleBackupRetention(ctx context.Context, req *nextgenv1.OverrideFlexibleBackupRetentionRequest) (*nextgenv1.BackupSetting, error) {
	resp := new(nextgenv1.BackupSetting)
	if err := s.mgmtSvcConn.Invoke(ctx, "/tidb_mgmt_service.v1.BackupService/OverrideFlexibleBackupRetention", req, resp); err != nil {
		return nil, err
	}
	return resp, nil
}

func (s *MgmtService) ListClusters(ctx context.Context, param ListClustersParam) ([]*mgmtv1.Cluster, int64, error) {
	filter := &mgmtv1.ClusterFilter{
		TenantIds:  param.TenantIDs,
		ProjectIds: param.ProjectIDs,
		ClusterIds: param.ClusterIDs,
		ExcludeStates: []v1beta11.ClusterState{
			v1beta11.ClusterState_DELETED,
		},
		ClusterType: param.DeployType,
	}
	if param.ServicePlan != nil {
		filter.ServicePlan = *param.ServicePlan
	}
	listReq := &mgmtv1.ListClustersRequest{
		Filter:   filter,
		PageSize: uint32(param.PerPage),
		Page:     uint32(param.Page),
	}
	listResp, err := s.Cluster.ListClusters(ctx, listReq)
	if err != nil {
		return nil, 0, err
	}

	countReq := &mgmtv1.CountClustersRequest{
		Filter: filter,
	}
	countResp, err := s.Cluster.CountClusters(ctx, countReq)
	if err != nil {
		return nil, 0, err
	}
	return listResp.GetClusters(), int64(countResp.GetCount()), nil
}

func (s *MgmtService) GetCloudIAMInfo(ctx context.Context, clusterID string) (*mgmtv1.CloudIamInfo, error) {
	iamInfo, err := s.Cluster.GetCloudIamInfo(ctx, &mgmtv1.GetCloudIamInfoRequest{
		ClusterId: clusterID,
	})
	if err != nil {
		return nil, err
	}

	return iamInfo, nil
}

func (s *MgmtService) GetResourcePoolString(ctx context.Context, resourcePoolID string) (*nextgenv1.ResourcePoolProfile, error) {
	idUint, err := strconv.ParseUint(resourcePoolID, 10, 64)
	if err != nil {
		return nil, fmt.Errorf("invalid resource_pool_id: %s", resourcePoolID)
	}
	return s.GetResourcePool(ctx, idUint)
}

func (s *MgmtService) GetResourcePool(ctx context.Context, resourcePoolID uint64) (*nextgenv1.ResourcePoolProfile, error) {
	req := &nextgenv1.GetResourcePoolRequest{
		ResourcePoolId: resourcePoolID,
		ShowDeleted:    true,
	}
	resp, err := s.ResourcePool.GetResourcePool(ctx, req)
	if err != nil {
		return nil, err
	}
	if resp == nil || resp.ResourcePool == nil {
		return nil, errs.APIErrorf(api.ErrCodeNotFound, "resource pool %d not found", resourcePoolID)
	}
	return resp, nil
}

func (s *MgmtService) UpdateRegionOpsAuditLogSetting(ctx context.Context, req *UpdateRegionOpsAuditLogSettingReq) error {
	request := &mgmtv1.UpdateRegionOpsAuditLogSettingRequest{
		DeployType:        mgmtv1.DeployType_DEPLOY_TYPE_NEXTGEN,
		RegionLogicalName: req.RegionLogicalName,
	}
	opsAuditLogSetting := &commonv1.RegionOpsAuditLogSetting{
		Enabled: req.Enabled,
	}
	if req.LogRotation != nil {
		opsAuditLogSetting.LogLocation = &commonv1.RegionOpsAuditLogSetting_LogLocation{
			BucketUri:  req.LogLocation.BucketUri,
			RoleArn:    req.LogLocation.RoleArn,
			AccountId:  req.LogLocation.AccountId,
			ExternalId: req.LogLocation.ExternalId,
		}
	}
	if req.LogRotation != nil {
		opsAuditLogSetting.LogRotation = &commonv1.RegionOpsAuditLogSetting_LogRotation{
			FileSizeLimitInMb:     req.LogRotation.FileSizeLimitInMb,
			RotationIntervalInMin: req.LogRotation.RotationIntervalInMin,
		}
	}
	request.OpsAuditLogSetting = opsAuditLogSetting
	_, err := s.Region.UpdateRegionOpsAuditLogSetting(ctx, request)
	return err
}

func (s *MgmtService) ListByocRegions(ctx context.Context, orgID uint64) (*mgmtv1.ListRegionsResponse, error) {
	resp, err := s.Region.ListRegions(ctx, &mgmtv1.ListRegionsRequest{
		DeployType:     mgmtv1.DeployType_DEPLOY_TYPE_NEXTGEN,
		ServicePlan:    commonv1.ServicePlan_BYOC,
		OrganizationId: orgID,
	})
	return resp, err
}

// CoreSvcCli is a wrapper of core.CoreServiceClient and add some helper methods
type CoreSvcCli struct {
	Core core.CoreServiceClient
	Mgmt *MgmtService
}

func NewCoreSvcCli(coreSvcAddr, mgmtSvcAddr, nextGenGlobalAddr string) (*CoreSvcCli, error) {
	c := &CoreSvcCli{}

	opts := grpcutil.PresetDailOption().Default()
	coreSvcConn, err := grpc.NewClient(coreSvcAddr, opts...)
	if err != nil {
		return nil, fmt.Errorf("dial core svc %s failed: %s", coreSvcAddr, err)
	}
	c.Core = core.NewCoreServiceClient(coreSvcConn)

	mgmtSvcConn, err := grpc.NewClient(mgmtSvcAddr, opts...)
	if err != nil {
		return nil, fmt.Errorf("dail mgmt svc %s failed: %s", mgmtSvcAddr, err)
	}
	nextGenGlobalconn, err := grpc.NewClient(nextGenGlobalAddr, opts...)
	if err != nil {
		return nil, err
	}
	c.Mgmt = &MgmtService{
		Cluster:                     mgmtv1.NewClusterServiceClient(mgmtSvcConn),
		ClusterCustomStatus:         mgmtv1.NewClusterCustomStatusServiceClient(mgmtSvcConn),
		Dataplane:                   mgmtv1.NewDataPlaneServiceClient(mgmtSvcConn),
		Maintenance:                 mgmtv1.NewMaintenanceServiceClient(mgmtSvcConn),
		SpuSku:                      mgmtv1.NewSpuServiceClient(mgmtSvcConn),
		Vpc:                         mgmtv1.NewVpcServiceClient(mgmtSvcConn),
		Region:                      mgmtv1.NewRegionServiceClient(mgmtSvcConn),
		PrivateLinkServiceMigration: mgmtv1.NewPrivateLinkServiceMigrationServiceClient(mgmtSvcConn),
		ResourcePool:                mgmtv1.NewResourcePoolServiceClient(mgmtSvcConn),
		ClusterMigration:            mgmtv1.NewMigrationClusterServiceClient(mgmtSvcConn),
		RcuQuota:                    mgmtv1.NewRcuQuotaServiceClient(mgmtSvcConn),
		DataSynchronization:         mgmtv1.NewDataSynchronizationServiceClient(mgmtSvcConn),
		ReservedCapacityTemplate:    mgmtv1.NewReservedCapacityTemplateServiceClient(mgmtSvcConn),
		DedicatedToPremiumPrecheck:  mgmtv1.NewDedicatedToPremiumPrecheckServiceClient(mgmtSvcConn),
		NextGenExport:               nextgenv1.NewExportServiceClient(nextGenGlobalconn),
		NextGenBackup:               nextgenv1.NewBackupServiceClient(nextGenGlobalconn),
		NextGenCluster:              nextgenv1.NewClusterServiceClient(nextGenGlobalconn),
		mgmtSvcConn:                 mgmtSvcConn,
	}

	return c, nil
}

// TODO: Deprecated: use Mgmt.GetCluster instead
func (c *CoreSvcCli) GetCluster(ctx context.Context, clusterID uint64, withDataPlaneInfo bool) (*core.Cluster, error) {
	resp, err := c.Core.ListClusters(ctx, &core.ListClustersReq{
		Filter: &core.ClusterFilter{
			ClusterIds: []uint64{clusterID},
		},
		WithDataPlaneInfo: withDataPlaneInfo,
	})
	if err != nil {
		return nil, err
	}
	if err := grpcutil.RespToErr(resp.BaseResp); err != nil {
		return nil, err
	}

	cluster, ok := resp.Clusters[clusterID]
	if !ok {
		return nil, errs.APIErrorf(api.ErrCodeNotFound, "cluster %d not found", clusterID)
	}

	return cluster, nil
}

func (c *CoreSvcCli) ListClusters(ctx context.Context, param ListClustersParam) ([]*core.Cluster, int64, error) {
	filter := &core.ClusterFilter{
		TenantIds:  param.TenantIDs,
		ProjectIds: param.ProjectIDs,
		ClusterIds: param.ClusterIDs,
		ExcludeStatuses: []core.ClusterStatus{
			core.ClusterStatus_CLUSTER_STATUS_DELETED,
			core.ClusterStatus_CLUSTER_STATUS_DELETECOMPLETE,
			core.ClusterStatus_CLUSTER_STATUS_CLEARED,
		},
		IsDevTier: param.IsDevTier,
	}
	sorter := &base.Sorter{
		OrderBy: "created_at",
		Sort:    base.Sort_SORT_DESC,
	}
	listReq := &core.ListClustersReq{
		Filter: filter,
		Pager: &base.Pager{
			Page:     param.Page,
			PageSize: param.PerPage,
		},
		Sorters: []*base.Sorter{sorter},
	}
	listResp, err := c.Core.ListClusters(ctx, listReq)
	if err != nil {
		return nil, 0, err
	}
	if err := grpcutil.RespToErr(listResp.BaseResp); err != nil {
		return nil, 0, err
	}

	countReq := &core.CountClustersReq{
		Filter: filter,
	}
	countResp, err := c.Core.CountClusters(ctx, countReq)
	if err != nil {
		return nil, 0, err
	}
	if err := grpcutil.RespToErr(countResp.BaseResp); err != nil {
		return nil, 0, err
	}

	clusters := make([]*core.Cluster, 0, len(listResp.Clusters))
	for _, cluster := range listResp.Clusters {
		clusters = append(clusters, cluster)
	}
	return clusters, countResp.Count, nil
}
