package infrahandler

import (
	"context"
	"fmt"
	"sync"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/constant"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/idl/pbgen/proto/core"
	"github.com/pingcap/dbaas/billing2/idl/pbgen/proto/dataflow"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"go.buf.build/protocolbuffers/go/linuxgit/tidbcloud/base"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
)

var (
	coreCli     core.CoreServiceClient
	coreSvcOnce sync.Once
)

func GetCoreProxySvcClient() core.CoreServiceClient {
	coreSvcOnce.Do(func() {
		conn, err := grpc.DialContext(
			context.Background(),
			config.Get().GetString(config.CoreProxySvcAddress),
			grpc.WithTransportCredentials(insecure.NewCredentials()),
		)
		if err != nil {
			err = billing_errors.NewSystemError(err)
			log.Error("init core proxy service client", zap.String("addr", config.Get().GetString(config.CoreProxySvcAddress)), zap.Error(err))
			panic(fmt.Sprintf("dial core-svc: %s", err))
		}

		coreCli = core.NewCoreServiceClient(conn)
	})

	return coreCli
}

type InfraHandlerImpl struct{}

func (*InfraHandlerImpl) GetCluster(tenantID, clusterID uint) (ret *forms.Cluster, err error) {
	if !config.Get().GetBool(config.EnableInfraHandler) {
		return &forms.Cluster{
			ID:       clusterID,
			Name:     constant.UnknownClusterName,
			TenantID: tenantID,
		}, nil
	}
	ctx := context.Background()

	client := GetCoreProxySvcClient()

	req := &core.ListClustersReq{
		Filter: &core.ClusterFilter{
			TenantIds:  []uint64{uint64(tenantID)},
			ClusterIds: []uint64{uint64(clusterID)},
		},
	}
	clusterResp, err := client.ListClusters(ctx, req)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	} else if clusterResp.BaseResp.ErrCode != 0 {
		return nil, billing_errors.NewSystemError(fmt.Errorf("invalid call core svc, err code %d", clusterResp.BaseResp.ErrCode))
	}
	cluster, ok := clusterResp.Clusters[uint64(clusterID)]
	if !ok {
		return nil, billing_errors.ObjectNotExistsError
	}
	return &forms.Cluster{
		ID:                clusterID,
		Name:              cluster.Name,
		TenantID:          tenantID,
		CloudProviderName: cluster.CloudProviderName,
		ProjectID:         uint(cluster.ProjectId),
		InfraProviderType: cluster.InfraProviderType,
		RegionName:        cluster.RegionName,
		RegionDisplayName: cluster.RegionDisplayName,
		IsDevTier:         cluster.IsDevTier,
		Status:            core.ClusterStatus_name[int32(cluster.Status)],
		DeletedAt:         cluster.DeletedAt,
	}, nil
}

func (*InfraHandlerImpl) listClusters(tenantID uint, isDevTier bool) (ret []*forms.Cluster, err error) {
	if !config.Get().GetBool(config.EnableInfraHandler) {
		return nil, nil
	}
	ctx := context.Background()

	client := GetCoreProxySvcClient()
	var clusters []*forms.Cluster
	const maxPage = 100
	const pageSize = 100
	for page := 1; page != maxPage; page++ {
		req := &core.ListClustersReq{
			Filter: &core.ClusterFilter{
				TenantIds: []uint64{uint64(tenantID)},
				IsDevTier: &isDevTier,
			},
			Pager: &base.Pager{
				Page:     int64(page),
				PageSize: pageSize,
			},
		}
		clusterResp, err := client.ListClusters(ctx, req)
		if err != nil {
			return nil, billing_errors.NewSystemError(err)
		} else if clusterResp.BaseResp.ErrCode != 0 {
			return nil, billing_errors.NewSystemError(fmt.Errorf("invalid call core svc, err code %d", clusterResp.BaseResp.ErrCode))
		}
		if len(clusterResp.Clusters) == 0 {
			break
		}
		for clusterID, cluster := range clusterResp.Clusters {
			clusters = append(clusters, &forms.Cluster{
				ID:                uint(clusterID),
				Name:              cluster.Name,
				TenantID:          tenantID,
				CloudProviderName: cluster.CloudProviderName,
				ProjectID:         uint(cluster.ProjectId),
				InfraProviderType: cluster.InfraProviderType,
				RegionName:        cluster.RegionName,
				RegionDisplayName: cluster.RegionDisplayName,
				IsDevTier:         cluster.IsDevTier,
				Status:            core.ClusterStatus_name[int32(cluster.Status)],
			})
		}
		if len(clusterResp.Clusters) < pageSize {
			break
		}
	}
	return clusters, nil
}

func (i *InfraHandlerImpl) ListClusters(tenantID uint) (ret []*forms.Cluster, err error) {
	var results []*forms.Cluster
	for _, isDevTier := range []bool{false, true} {
		clusters, err := i.listClusters(tenantID, isDevTier)
		if err != nil {
			return nil, err
		}
		results = append(results, clusters...)
	}
	return results, nil
}

func (i *InfraHandlerImpl) ListDedicatedClusters(tenantID uint) (ret []*forms.Cluster, err error) {
	return i.listClusters(tenantID, false)
}

func (*InfraHandlerImpl) CleanupTenantDedicateClusters(tenantID uint) error {
	if !config.Get().GetBool(config.EnableInfraHandler) {
		return nil
	}

	ctx := metadata.AppendToOutgoingContext(context.Background(), "api-key", config.Get().GetString(config.CoreSVCAPIKey))

	client := GetCoreProxySvcClient()

	req := &core.CleanClustersReq{
		TenantId:  uint64(tenantID),
		IsDevTier: false,
	}
	resp, e := client.CleanClusters(ctx, req)
	var err error = nil
	if e != nil {
		err = billing_errors.NewSystemError(e)
	} else {
		if resp.BaseResp.ErrCode != 0 {
			err = billing_errors.NewSystemError(fmt.Errorf("invalid call core svc, err code %d", resp.BaseResp.ErrCode))
		}
	}

	if err != nil {
		log.Error("failed call grpc clean tenant dedicate clusters", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}

	log.Info("success call grpc clean tenant dedicate clusters", zap.Uint("tenantID", tenantID))

	return nil
}

func (*InfraHandlerImpl) CleanupTenantDevTierClusters(tenantID uint) error {
	if !config.Get().GetBool(config.EnableInfraHandler) {
		return nil
	}

	ctx := metadata.AppendToOutgoingContext(context.Background(), "api-key", config.Get().GetString(config.CoreSVCAPIKey))

	client := GetCoreProxySvcClient()

	req := &core.CleanClustersReq{
		TenantId:  uint64(tenantID),
		IsDevTier: true,
	}
	resp, e := client.CleanClusters(ctx, req)
	var err error = nil
	if e != nil {
		err = billing_errors.NewSystemError(e)
	} else {
		if resp.BaseResp.ErrCode != 0 {
			err = billing_errors.NewSystemError(fmt.Errorf("invalid call core svc, err code %d", resp.BaseResp.ErrCode))
		}
	}

	if err != nil {
		log.Error("failed call grpc clean tenant dev tier clusters", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}

	log.Info("success call grpc clean tenant dev tier clusters", zap.Uint("tenantID", tenantID))

	return nil
}

var (
	backupCli     dataflow.BackupInternalServiceClient
	backupSvcOnce sync.Once
)

func GetBackupSvcClient() dataflow.BackupInternalServiceClient {
	backupSvcOnce.Do(func() {
		ctx := context.Background()
		addr := config.Get().GetString(config.DataflowSvcAddress)
		conn, err := grpc.DialContext(
			ctx,
			addr,
			grpc.WithTransportCredentials(insecure.NewCredentials()),
		)

		if err != nil {
			err = billing_errors.NewSystemError(err)
			log.Error("init dataflow backup service client", zap.String("addr", addr), zap.Error(err))
			panic(fmt.Sprintf("dial backup-svc: %s", err))
		}

		backupCli = dataflow.NewBackupInternalServiceClient(conn)

	})
	return backupCli
}

func (*InfraHandlerImpl) CleanupTenantBackups(tenantID uint) error {
	if !config.Get().GetBool(config.EnableInfraHandler) {
		return nil
	}

	ctx := context.Background()
	client := GetBackupSvcClient()
	req := &dataflow.DeleteAllManualBackupsReq{
		OrgId: uint64(tenantID),
	}
	_, err := client.DeleteAllManualBackups(ctx, req)
	if err != nil {
		err = billing_errors.NewSystemError(err)
		log.Error("failed call grpc delete all manual backups", zap.Uint("tenantID", tenantID), zap.Error(err))
		return err
	}

	log.Info("success call delete all manual backups", zap.Uint("tenantID", tenantID))
	return nil
}
