package infrainfo

import (
	"context"
	"fmt"
	"sync"

	"github.com/go-logr/logr"
	"github.com/tidbcloud/core-svc/idl/pbgen/proto/core"
	dedicatedv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"

	"github.com/tidbcloud/devops/entity/k8supgrade"
	"github.com/tidbcloud/devops/pkg/infra/clients/coresvccli"
	"github.com/tidbcloud/devops/pkg/infra/clients/mgmthttpcli"
	"github.com/tidbcloud/devops/pkg/infra/errs"
)

var _ ServiceAPI = &threeLayerArchSvc{}

// threeLayerArchSvc is the service for getting k8s infra information from the 3-layer architecture.
type threeLayerArchSvc struct {
	coreSvcCli *coresvccli.CoreSvcCli
	mgmtHTTP   mgmthttpcli.MgmtHTTPClient
	regionsMap sync.Map
	logger     logr.Logger
}

func (svc *threeLayerArchSvc) ListK8SInformation(ctx context.Context, req *ListK8SInformationReq) (list k8supgrade.K8sInfoList, err error) {
	svc.logger.Info("ListK8SInformation", "req", req)

	var regions []string
	val, ok := svc.regionsMap.Load(req.CloudProvider)
	if !ok {
		resp, err := svc.coreSvcCli.Core.ListCloudRegions(ctx, &core.ListCloudRegionsReq{
			Filter: &core.CloudRegionFilter{CloudProviderNames: []string{req.CloudProvider}},
		})
		if err != nil {
			return k8supgrade.K8sInfoList{}, fmt.Errorf("failed to list cloud regions: %w", err)
		}
		regions = make([]string, 0, len(resp.Regions))
		for _, region := range resp.Regions {
			if region.Status == core.CloudRegion_STATUS_ACTIVE {
				regions = append(regions, region.Name)
			}
		}
		svc.regionsMap.Store(req.CloudProvider, regions)
	} else {
		regions = val.([]string)
	}
	svc.logger.Info("get regions before ListK8SInformation", "regions", regions)

	results := make(chan *dedicatedv1.K8SInformationList, len(regions))
	var wg sync.WaitGroup
	for _, region := range regions {
		wg.Add(1)
		go func(region string) {
			defer wg.Done()
			res, e := svc.mgmtHTTP.RegionServer(region, req.CloudProvider).ListK8SInformation(ctx, &dedicatedv1.ListK8SInformationRequest{
				Version: req.Version,
			})
			if e != nil {
				svc.logger.Error(e, "failed to list k8s information", "region", region, "cloudProvider", req.CloudProvider)
			} else {
				results <- res
			}
		}(region)
	}

	go func() {
		wg.Wait()
		close(results)
	}()

	list.Items = make([]*k8supgrade.K8sInfo, 0, len(regions))
	for res := range results {
		for _, item := range res.K8SInfos {
			list.Items = append(list.Items, convertK8sInfo(item))
		}
		list.Total += int(res.Total)
	}
	return list, nil
}

func (svc *threeLayerArchSvc) GetK8sInformation(ctx context.Context, req *GetK8SInformationReq) (info *k8supgrade.K8sInfo, err error) {
	svc.logger.Info("GetK8sInformation", "req", req)
	res, err := svc.mgmtHTTP.RegionServer(req.Region, req.CloudProvider).GetK8sInformation(ctx, &dedicatedv1.GetK8SInformationRequest{
		Name: req.Name,
	})
	if err != nil {
		svc.logger.Error(err, "failed to get k8s information", "region", req.Region, "cloudProvider", req.CloudProvider, "name", req.Name)
		return nil, err
	}
	return convertK8sInfo(res), nil
}

func convertK8sInfo(info *dedicatedv1.K8SInformation) *k8supgrade.K8sInfo {
	return &k8supgrade.K8sInfo{
		CloudProvider:        info.CloudProvider,
		CloudAccount:         info.CloudAccount,
		Region:               info.Region,
		ProjectID:            info.ProjectId,
		TenantID:             info.TenantId,
		K8sName:              info.K8SName,
		ControlPlaneVersion:  k8supgrade.NewMustVersion(info.ControlPlaneVersion),
		NumberOfTiDBClusters: int(info.NumTidbClusters),
		AdminNodes:           convertNodes(info.AdminNodes),
		TiDBClusters:         convertClusters(info.TidbClusters),
		Is3LayerArch:         true,
		AppboxName:           info.AppboxName,
		AppboxNamespace:      info.AppboxNamespace,
		O11YK8SNodes:         convertNodes(info.O11YK8SNodes),
	}
}

func convertNodes(nodes []*dedicatedv1.NodeGroup) []*k8supgrade.Node {
	result := make([]*k8supgrade.Node, 0, len(nodes))
	for _, node := range nodes {
		result = append(result, &k8supgrade.Node{
			Kind:           node.Kind,
			Count:          int(node.Count),
			Version:        k8supgrade.NewMustVersion(node.Version),
			MaxSkewVersion: int(node.MaxSkewVersion),
		})
	}
	return result
}

func convertClusters(clusters []*dedicatedv1.TiDBClusterInfraInformation) []*k8supgrade.TiDBClusterInfraInfo {
	result := make([]*k8supgrade.TiDBClusterInfraInfo, 0, len(clusters))
	for _, cluster := range clusters {
		topo := make(map[string]int, len(cluster.Topology))
		for k, v := range cluster.Topology {
			topo[k] = int(v)
		}
		compNodes := convertNodes(cluster.ComponentNodes)
		result = append(result, &k8supgrade.TiDBClusterInfraInfo{
			ID:             cluster.ClusterId,
			Version:        cluster.Version,
			Status:         cluster.Version,
			Topology:       topo,
			ComponentNodes: compNodes,
			Notes:          cluster.Notes,
		})
	}
	return result
}

func (svc *threeLayerArchSvc) GetUpgradeOperation(ctx context.Context, req *CommonUpgradeOperationReq) (*k8supgrade.UpgradeOperation, error) {
	svc.logger.Info("GetUpgradeOperation", "req", req)

	pbUO, err := svc.mgmtHTTP.RegionServer(req.Region, req.CloudProvider).GetUpgradeOperation(ctx, &dedicatedv1.GetUpgradeOperationRequest{
		Name: req.Name,
	})
	if err != nil {
		svc.logger.Error(err, "failed to get upgrade operation", "region", req.Region, "cloudProvider", req.CloudProvider, "name", req.Name)
		return nil, err
	}
	return pbUO2Entity(pbUO, req.Namespace), nil
}

func (svc *threeLayerArchSvc) PauseUpgradeOperation(ctx context.Context, req *CommonUpgradeOperationReq) error {
	svc.logger.Info("PauseUpgradeOperation", "req", req)

	//TODO: support pause upgrade operation
	return svc.mgmtHTTP.RegionServer(req.Region, req.CloudProvider).UpdateUpgradeOperation(ctx, &dedicatedv1.UpdateUpgradeOperationRequest{
		Name:             req.Name,
		UpgradeOperation: &dedicatedv1.UpgradeOperation{},
	})
}

func (svc *threeLayerArchSvc) DeleteUpgradeOperation(ctx context.Context, req *CommonUpgradeOperationReq) error {
	svc.logger.Info("DeleteUpgradeOperation", "req", req)
	return svc.mgmtHTTP.RegionServer(req.Region, req.CloudProvider).DeleteUpgradeOperation(ctx, &dedicatedv1.DeleteUpgradeOperationRequest{
		Name: req.Name,
	})
}

func (svc *threeLayerArchSvc) CreateOrUpdateUpgradeOperation(ctx context.Context, req *CommonUpgradeOperationReq) error {
	svc.logger.Info("CreateOrUpdateUpgradeOperation", "req", req)

	_, err := svc.mgmtHTTP.RegionServer(req.Region, req.CloudProvider).GetUpgradeOperation(ctx, &dedicatedv1.GetUpgradeOperationRequest{
		Name: req.Name,
	})
	if err != nil {
		if errs.IsNotFound(err) {
			return svc.mgmtHTTP.RegionServer(req.Region, req.CloudProvider).CreateUpgradeOperation(ctx, &dedicatedv1.CreateUpgradeOperationRequest{
				UpgradeOperation: entity2PBUO(req.UpgradeOperation),
			})
		}

		svc.logger.Error(err, "failed to get upgrade operation", "region", req.Region, "cloudProvider", req.CloudProvider, "name", req.Name)
		return err
	}

	return svc.mgmtHTTP.RegionServer(req.Region, req.CloudProvider).UpdateUpgradeOperation(ctx, &dedicatedv1.UpdateUpgradeOperationRequest{
		Name:             req.Name,
		UpgradeOperation: entity2PBUO(req.UpgradeOperation),
	})
}

func (svc *threeLayerArchSvc) PatchUpgradeOperationAnnotations(ctx context.Context, req *PatchUpgradeOperationAnnotationsReq) error {
	svc.logger.Info("PatchUpgradeOperationAnnotations", "req", req)
	return svc.mgmtHTTP.RegionServer(req.Region, req.CloudProvider).PatchUpgradeOperationAnnotations(ctx, &dedicatedv1.PatchUpgradeOperationAnnotationsRequest{
		Name:        req.Name,
		Annotations: cloneStringMap(req.Annotations),
	})
}
