package service

import (
	"context"
	"fmt"

	asappsv1 "github.com/pingcap/advanced-statefulset/client/apis/apps/v1"
	"github.com/pingcap/tidb-operator/pkg/apis/label"
	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-agent/v1"
	apicommon "github.com/tidbcloud/infra-provider/api/tidbcloud/common"
)

func (s *AgentService) DeleteASTSAndResumeTC(ctx context.Context, req *apiv1.DeleteASTSAndResumeTCReq) (*apiv1.DeleteASTSAndResumeTCResp, error) {
	logger := tilog.WithContextS(ctx).With("cluster_id", req.ClusterId)
	logger.Info("DeleteASTSAndResumeTC", zap.Any("req", req))

	deletedASTSNames, err := s.deleteASTS(ctx, &deleteAstsParams{
		clusterId:           req.ClusterId,
		componentTypes:      req.ComponentTypes,
		deletionPropagation: metav1.DeletePropagationForeground,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to delete asts: %w", err)
	}
	if req.OnlyDeleteAsts {
		return &apiv1.DeleteASTSAndResumeTCResp{
			DeletedAstsNames: deletedASTSNames,
		}, nil
	}

	resumedTCNames, err := s.resumeTC(ctx, &resumeTCParams{
		clusterId: req.ClusterId,
		all:       true,
	})
	if err != nil {
		return nil, fmt.Errorf("failed to resume tc: %w", err)
	}

	return &apiv1.DeleteASTSAndResumeTCResp{
		DeletedAstsNames: deletedASTSNames,
		ResumedTcNames:   resumedTCNames,
	}, nil
}

type deleteAstsParams struct {
	clusterId           string
	componentTypes      []apicommon.ComponentType
	deletionPropagation metav1.DeletionPropagation
}

func (s *AgentService) deleteASTS(ctx context.Context, params *deleteAstsParams) (deletedASTSNames []string, _ error) {
	logger := tilog.WithContextS(ctx).With("cluster_id", params.clusterId)
	logger.Info("delete asts")
	astsList, err := s.getASTSsByClusterId(ctx, params.clusterId)
	if err != nil {
		return nil, err
	}

	for _, asts := range astsList {
		if !lo.Contains(params.componentTypes, convertComponentTypeToAPI(asts.Labels[label.ComponentLabelKey])) {
			logger.Info("skip for component", zap.String("component", asts.Labels[label.ComponentLabelKey]))
			continue
		}

		if err := s.dataplaneCli.Delete(ctx, asts, client.PropagationPolicy(params.deletionPropagation)); err != nil {
			return nil, fmt.Errorf("failed to delete asts(%s/%s): %w", asts.Namespace, asts.Name, err)
		}
		deletedASTSNames = append(deletedASTSNames, asts.Name)
		logger.Info("deleted asts", zap.String("ns", asts.Namespace), zap.String("name", asts.Name))
	}

	return deletedASTSNames, nil
}

// nolint: unused
func (s *AgentService) getASTSsByClusterId(ctx context.Context, clusterId string) (map[string]*asappsv1.StatefulSet, error) {
	ns := buildNamespace(clusterId)

	labelSelector, err := metav1.LabelSelectorAsSelector(&metav1.LabelSelector{
		MatchExpressions: []metav1.LabelSelectorRequirement{
			{
				Key:      label.ComponentLabelKey,
				Operator: metav1.LabelSelectorOpIn,
				Values:   clusterComponents,
			},
		},
	})
	if err != nil {
		return nil, fmt.Errorf("build label selector: %w", err)
	}
	var astsList asappsv1.StatefulSetList
	if err := s.dataplaneCli.List(ctx, &astsList, client.InNamespace(ns), client.MatchingLabelsSelector{Selector: labelSelector}); err != nil {
		return nil, fmt.Errorf("failed to list pods: %w", err)
	}
	out := lo.SliceToMap(astsList.Items, func(item asappsv1.StatefulSet) (string, *asappsv1.StatefulSet) {
		return item.Name, &item
	})
	return out, nil
}
