package privatelinkservicemigration

import (
	"context"
	"fmt"
	"strings"

	"github.com/pingcap/log"
	"github.com/samber/lo"
	clusterctlpb "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	regionalapiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/clustercli"
	bizerr "github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/common/utils"
	rsconfig "github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/approute"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/privatelinkservice"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/resourcemanager/privatelinkservicemigration"
	"go.uber.org/zap"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type PrivateLinkServiceMigrationService struct {
	privatelinkservicemigrationrw privatelinkservicemigration.PrivateLinkServiceMigrationReaderWriter
	privatelinkservicerw          privatelinkservice.PrivateLinkServiceReaderWriter
	approuter                     approute.APPRouteReader
	clusterCli                    clustercli.ClusterClient
	config                        rsconfig.RootConfig
}

func NewPrivateLinkServiceMigrationService(
	privatelinkservicemigrationrw privatelinkservicemigration.PrivateLinkServiceMigrationReaderWriter,
	privatelinkservicerw privatelinkservice.PrivateLinkServiceReaderWriter,
	approuter approute.APPRouteReader,
	clusterCli clustercli.ClusterClient,
	config rsconfig.RootConfig,
) *PrivateLinkServiceMigrationService {
	return &PrivateLinkServiceMigrationService{
		privatelinkservicemigrationrw: privatelinkservicemigrationrw,
		privatelinkservicerw:          privatelinkservicerw,
		approuter:                     approuter,
		clusterCli:                    clusterCli,
		config:                        config,
	}
}

func (s *PrivateLinkServiceMigrationService) CreatePrivateLinkServiceMigration(ctx context.Context, req *regionalapiv1.CreatePrivateLinkServiceMigrationRequest) (*regionalapiv1.PrivateLinkServiceMigration, error) {
	plsList := &v1alpha1.PrivateLinkServiceList{}
	if err := s.privatelinkservicerw.List(ctx, plsList, client.MatchingLabels{
		v1alpha1.ClusterIDKey:      req.PrivateLinkServiceMigration.ClusterId,
		v1alpha1.LoadBalancerIDKey: req.PrivateLinkServiceMigration.TidbGroupId,
	}); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list private link services")
	}
	activePls := lo.Filter(plsList.Items, func(item v1alpha1.PrivateLinkService, _ int) bool {
		return item.Status.Phase == v1alpha1.PrivateLinkServicePhaseActive
	})
	if len(activePls) != 1 {
		return nil, bizerr.New(bizerr.ParameterInvalid, "failed to find active private link service")
	}
	pls := activePls[0]

	targetPlsList := &v1alpha1.PrivateLinkServiceList{}
	if err := s.privatelinkservicerw.List(ctx, targetPlsList, client.MatchingLabels{
		v1alpha1.ClusterIDKey:      req.PrivateLinkServiceMigration.TargetClusterId,
		v1alpha1.LoadBalancerIDKey: req.PrivateLinkServiceMigration.TargetTidbGroupId,
	}); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list target private link services")
	}
	activeTargetPls := lo.Filter(targetPlsList.Items, func(item v1alpha1.PrivateLinkService, _ int) bool {
		return item.Status.Phase == v1alpha1.PrivateLinkServicePhaseActive
	})
	if len(activeTargetPls) > 0 {
		return nil, bizerr.New(bizerr.ParameterInvalid, "target private link service already exists")
	}
	plsMpb := &regionalapiv1.PrivateLinkServiceMigration{
		Id:                   req.PrivateLinkServiceMigration.Id,
		ClusterId:            req.PrivateLinkServiceMigration.ClusterId,
		TidbGroupId:          req.PrivateLinkServiceMigration.TidbGroupId,
		PrivateLinkServiceId: pls.Name,
		ProjectId:            req.PrivateLinkServiceMigration.ProjectId,
		TargetClusterId:      req.PrivateLinkServiceMigration.TargetClusterId,
		TargetTidbGroupId:    req.PrivateLinkServiceMigration.TargetTidbGroupId,
		Status:               regionalapiv1.PrivateLinkServiceMigration_STATUS_PENDING,
	}

	// make sure target group lb dns is same to pls dns name hash
	targetLb := v1alpha1.LoadBalancer{
		ID:        req.PrivateLinkServiceMigration.TargetTidbGroupId,
		ClusterID: req.PrivateLinkServiceMigration.TargetClusterId,
	}
	targetLbStatus, err := s.getLoadBalancer(targetLb)
	if err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to get target loadbalancer")
	}
	if targetLbStatus.Phase != v1alpha1.LoadBalancerPhaseActive {
		return nil, bizerr.New(bizerr.ParameterInvalid, "target loadbalancer is not active")
	}
	if !strings.HasSuffix(targetLbStatus.Host, fmt.Sprintf("%s.%s", pls.Status.DNSIdentify, s.config.PublicHostedZoneDomain)) {
		return nil, bizerr.New(bizerr.ParameterInvalid, "target loadbalancer is not same to pls dns name suffix")
	}
	eerr := s.privatelinkservicemigrationrw.Get(ctx, client.ObjectKey{Name: req.PrivateLinkServiceMigration.Id, Namespace: pls.Namespace}, &v1alpha1.PrivateLinkServiceMigration{})
	if eerr == nil {
		return nil, bizerr.New(bizerr.ResourceConflict, fmt.Sprintf("private link service migration %s already exists", req.PrivateLinkServiceMigration.Id))
	}
	if !apierrors.IsNotFound(eerr) {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to get private link service migration")
	}

	if req.ValidateOnly {
		return plsMpb, nil
	}

	plsMigration := &v1alpha1.PrivateLinkServiceMigration{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: pls.Namespace,
			Name:      req.PrivateLinkServiceMigration.Id,
			Labels: map[string]string{
				v1alpha1.ProjectIDKey:         req.PrivateLinkServiceMigration.ProjectId,
				v1alpha1.TenantIDKey:          pls.Labels[v1alpha1.TenantIDKey],
				v1alpha1.SourceClusterIDKey:   req.PrivateLinkServiceMigration.ClusterId,
				v1alpha1.SourceTidbGroupIDKey: req.PrivateLinkServiceMigration.TidbGroupId,
				v1alpha1.TargetClusterIDKey:   req.PrivateLinkServiceMigration.TargetClusterId,
				v1alpha1.TargetTidbGroupIDKey: req.PrivateLinkServiceMigration.TargetTidbGroupId,
				// tags for billing
				v1alpha1.TagProjectKey: req.PrivateLinkServiceMigration.ProjectId,
				v1alpha1.TagTenantKey:  pls.Labels[v1alpha1.TenantIDKey],
			},
		},
		Spec: v1alpha1.PrivateLinkServiceMigrationSpec{
			ClusterID:                   req.PrivateLinkServiceMigration.ClusterId,
			TidbGroupID:                 req.PrivateLinkServiceMigration.TidbGroupId,
			PrivateLinkServiceNamespace: pls.Namespace,
			PrivateLinkServiceName:      pls.Name,
			ProjectID:                   req.PrivateLinkServiceMigration.ProjectId,
			TargetClusterID:             req.PrivateLinkServiceMigration.TargetClusterId,
			TargetTidbGroupID:           req.PrivateLinkServiceMigration.TargetTidbGroupId,
		},
	}
	if err := s.privatelinkservicemigrationrw.Create(ctx, plsMigration); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to create private link service migration")
	}

	return plsMpb, nil
}

func (s *PrivateLinkServiceMigrationService) GetPrivateLinkServiceMigration(ctx context.Context, req *regionalapiv1.GetPrivateLinkServiceMigrationRequest) (*regionalapiv1.PrivateLinkServiceMigration, error) {
	namer := utils.NewNamer("", "", "", s.config.RegionConfig.Name, s.config.Env)
	plsMigration := &v1alpha1.PrivateLinkServiceMigration{}
	if err := s.privatelinkservicemigrationrw.Get(ctx, client.ObjectKey{Name: req.Id, Namespace: namer.GetNamespace()}, plsMigration); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to get private link service migration")
	}
	return convertToPrivateLinkServiceMigrationPB(plsMigration), nil
}

func (s *PrivateLinkServiceMigrationService) ListPrivateLinkServiceMigrations(ctx context.Context, req *regionalapiv1.ListPrivateLinkServiceMigrationsRequest) (*regionalapiv1.ListPrivateLinkServiceMigrationsResponse, error) {
	plsMigrationList := &v1alpha1.PrivateLinkServiceMigrationList{}
	if err := s.privatelinkservicemigrationrw.List(ctx, plsMigrationList, client.MatchingLabels{
		v1alpha1.ProjectIDKey: req.ProjectId,
	}); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, "failed to list private link service migrations")
	}
	return &regionalapiv1.ListPrivateLinkServiceMigrationsResponse{
		PrivateLinkServiceMigrations: lo.Map(plsMigrationList.Items, func(item v1alpha1.PrivateLinkServiceMigration, _ int) *regionalapiv1.PrivateLinkServiceMigration {
			return convertToPrivateLinkServiceMigrationPB(&item)
		}),
	}, nil
}

func convertToPrivateLinkServiceMigrationPB(plsMigration *v1alpha1.PrivateLinkServiceMigration) *regionalapiv1.PrivateLinkServiceMigration {
	plsmPB := &regionalapiv1.PrivateLinkServiceMigration{
		Id:                   plsMigration.Name,
		ClusterId:            plsMigration.Spec.ClusterID,
		TidbGroupId:          plsMigration.Spec.TidbGroupID,
		PrivateLinkServiceId: plsMigration.Spec.PrivateLinkServiceName,
		ProjectId:            plsMigration.Spec.ProjectID,
		TargetClusterId:      plsMigration.Spec.TargetClusterID,
		TargetTidbGroupId:    plsMigration.Spec.TargetTidbGroupID,
		Message:              plsMigration.Status.Reason,
	}

	switch plsMigration.Status.Phase {
	case v1alpha1.PrivateLinkServiceMigrationPhaseNone:
		plsmPB.Status = regionalapiv1.PrivateLinkServiceMigration_STATUS_PENDING
	case v1alpha1.PrivateLinkServiceMigrationPhasePending:
		plsmPB.Status = regionalapiv1.PrivateLinkServiceMigration_STATUS_PENDING
	case v1alpha1.PrivateLinkServiceMigrationPhaseRunning:
		plsmPB.Status = regionalapiv1.PrivateLinkServiceMigration_STATUS_RUNNING
	case v1alpha1.PrivateLinkServiceMigrationPhaseCompleted:
		plsmPB.Status = regionalapiv1.PrivateLinkServiceMigration_STATUS_COMPLETED
	case v1alpha1.PrivateLinkServiceMigrationPhaseFailed:
		plsmPB.Status = regionalapiv1.PrivateLinkServiceMigration_STATUS_FAILED
	}

	return plsmPB
}

func (r *PrivateLinkServiceMigrationService) getLoadBalancer(lb v1alpha1.LoadBalancer) (v1alpha1.LoadBalancerStatus, error) {
	info, err := r.clusterCli.GetTidbGroup(context.Background(), &clusterctlpb.GetTidbGroupRequest{
		ClusterId:   lb.ClusterID,
		TidbGroupId: lb.ID,
	})
	if err != nil {
		log.Error("get loadbalancer failed", zap.Any("clusterId", lb.ClusterID), zap.Any("loadbalancerId", lb.ID), zap.Error(err))
		return v1alpha1.LoadBalancerStatus{}, err
	}
	if info == nil {
		log.Error("loadbalancer not found", zap.Any("clusterId", lb.ClusterID), zap.Any("loadbalancerId", lb.ID))
		return v1alpha1.LoadBalancerStatus{}, fmt.Errorf("loadbalancer not found, clusterId: %s, loadbalancerId: %s", lb.ClusterID, lb.ID)
	}
	if !info.EnablePrivateLb || info.PrivateLb == nil {
		log.Error("private loadbalancer not found", zap.Any("clusterId", lb.ClusterID), zap.Any("loadbalancerId", lb.ID))
		return v1alpha1.LoadBalancerStatus{}, fmt.Errorf("private loadbalancer not found, clusterId: %s, loadbalancerId: %s", lb.ClusterID, lb.ID)
	}
	phase := v1alpha1.LoadBalancerPhaseActive
	if info.PrivateLb.Host == "" || info.PrivateLb.CloudResourceId == "" {
		log.Info("loadbalancer is not ready", zap.Any("clusterId", lb.ClusterID), zap.Any("loadbalancerId", lb.ID), zap.Any("host", info.PrivateLb.Host), zap.Any("cloudResourceId", info.PrivateLb.CloudResourceId))
		phase = v1alpha1.LoadBalancerPhaseCreating
	}
	return v1alpha1.LoadBalancerStatus{
		ID:              lb.ID,
		CloudResourceId: info.PrivateLb.CloudResourceId,
		Port:            info.PrivateLb.Port,
		Host:            info.PrivateLb.Host,
		Phase:           phase,
	}, nil
}
