package service

import (
	"context"
	"fmt"
	"strings"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"

	"github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/controller-runtime/pkg/client"

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

type operatorV2Service struct {
	apiv1.UnimplementedOperatorV2ServiceServer

	dataplaneCli client.Client
}

func NewOperatorV2Service(dataplaneCli client.Client) *operatorV2Service {
	return &operatorV2Service{
		dataplaneCli: dataplaneCli,
	}
}

func (s *operatorV2Service) PauseCluster(ctx context.Context, req *apiv1.PauseClusterReq) (*emptypb.Empty, error) {
	logger := tilog.WithContextL(ctx).With(zap.String("namespace", req.Namespace), zap.String("cluster_name", req.ClusterName))
	logger.Info("PauseCluster called")

	cluster, err := validateOperatorV2Cluster(ctx, s.dataplaneCli, req.Namespace, req.ClusterName)
	if err != nil {
		logger.Error("PauseCluster validation failed", zap.Error(err))
		return nil, err
	}

	logger.Info("Setting cluster to paused state")
	if cluster.Spec.Paused {
		logger.Info("Cluster is already paused")
		return &emptypb.Empty{}, nil
	}
	cluster.Spec.Paused = true
	if err := s.dataplaneCli.Update(ctx, cluster); err != nil {
		logger.Error("Failed to update cluster", zap.Error(err))
		return nil, status.Error(codes.Internal, "failed to update cluster: "+err.Error())
	}

	logger.Info("PauseCluster completed successfully")
	return &emptypb.Empty{}, nil
}

func (s *operatorV2Service) ResumeCluster(ctx context.Context, req *apiv1.ResumeClusterReq) (*emptypb.Empty, error) {
	logger := tilog.WithContextL(ctx).With(zap.String("namespace", req.Namespace), zap.String("cluster_name", req.ClusterName))
	logger.Info("ResumeCluster called", zap.Any("req", req))

	cluster, err := validateOperatorV2Cluster(ctx, s.dataplaneCli, req.Namespace, req.ClusterName)
	if err != nil {
		logger.Error("ResumeCluster validation failed", zap.Error(err))
		return nil, err
	}

	logger.Info("Setting cluster to resumed state")
	if !cluster.Spec.Paused {
		logger.Info("Cluster is already resumed")
		return &emptypb.Empty{}, nil
	}
	cluster.Spec.Paused = false
	if err := s.dataplaneCli.Update(ctx, cluster); err != nil {
		logger.Error("Failed to update cluster", zap.Error(err))
		return nil, status.Error(codes.Internal, "failed to update cluster: "+err.Error())
	}

	logger.Info("ResumeCluster completed successfully")
	return &emptypb.Empty{}, nil
}

func (s *operatorV2Service) DeleteGroupInstances(ctx context.Context, req *apiv1.DeleteGroupInstancesReq) (*emptypb.Empty, error) {
	logger := tilog.WithContextL(ctx).With(
		zap.String("namespace", req.Namespace),
		zap.String("component_name", req.ComponentName),
		zap.String("group_name", req.GroupName),
	)
	logger.Info("DeleteGroupInstances called")

	// validate namespace
	if req.Namespace == "" {
		return nil, status.Error(codes.InvalidArgument, "namespace is required")
	}
	// validate component name
	if req.ComponentName == "" {
		return nil, status.Error(codes.InvalidArgument, "component name is required")
	}
	if !lo.Contains(DeleteGroupInstancesApi_AllowComponents, req.ComponentName) {
		return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid component name: %s, should be one of %v", req.ComponentName, DeleteGroupInstancesApi_AllowComponents))
	}
	// validate group name
	if req.GroupName == "" {
		return nil, status.Error(codes.InvalidArgument, "group name is required")
	}

	// get Kind
	componentGroupKind, ok := ComponentGroupKindMap[strings.ToLower(req.ComponentName)]
	if !ok {
		return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid component name: %s, should be one of %v", req.ComponentName, lo.Keys(ComponentGroupKindMap)))
	}
	opv2Scheme := s.dataplaneCli.Scheme()

	// validate group exists
	runtimeGroupObj, err := opv2Scheme.New(schema.GroupVersionKind{
		Group:   CoreGroup,
		Version: CoreVersion,
		Kind:    componentGroupKind,
	})
	if err != nil {
		return nil, status.Error(codes.Internal, "failed to create group object: "+err.Error())
	}
	groupObj, ok := runtimeGroupObj.(client.Object)
	if !ok {
		return nil, status.Error(codes.Internal, "failed to convert runtime group object to client.Object")
	}

	logger.Info("DeleteGroupInstances - Getting group object",
		zap.String("namespace", req.Namespace),
		zap.String("group_name", req.GroupName))
	err = s.dataplaneCli.Get(ctx, client.ObjectKey{
		Namespace: req.Namespace,
		Name:      req.GroupName,
	}, groupObj)
	if err != nil {
		if apierrors.IsNotFound(err) {
			logger.Error("DeleteGroupInstances - Group not found",
				zap.String("group_kind", componentGroupKind),
				zap.String("group_name", req.GroupName),
				zap.String("namespace", req.Namespace))
			return nil, status.Error(codes.NotFound, fmt.Sprintf("%s(%s) not found in namespace %s", componentGroupKind, req.GroupName, req.Namespace))
		}
		logger.Error("DeleteGroupInstances - Failed to get group",
			zap.String("group_kind", componentGroupKind),
			zap.String("group_name", req.GroupName),
			zap.String("namespace", req.Namespace),
			zap.Error(err))
		return nil, status.Error(codes.Internal, fmt.Sprintf("failed to get %s(%s) in namespace %s: %s", componentGroupKind, req.GroupName, req.Namespace, err.Error()))
	}

	// get instances
	componentGroupInstanceListKind, ok := ComponentGroupInstanceListKindMap[req.ComponentName]
	if !ok {
		return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("invalid component name: %s, should be one of %v", req.ComponentName, lo.Keys(ComponentGroupInstanceListKindMap)))
	}
	runtimeInstanceObjList, err := opv2Scheme.New(schema.GroupVersionKind{
		Group:   CoreGroup,
		Version: CoreVersion,
		Kind:    componentGroupInstanceListKind,
	})
	if err != nil {
		return nil, status.Error(codes.Internal, "failed to create instance object list: "+err.Error())
	}
	instanceObjList, ok := runtimeInstanceObjList.(client.ObjectList)
	if !ok {
		return nil, status.Error(codes.Internal, "failed to convert runtime instance object list to client.ObjectList")
	}

	logger.Info("DeleteGroupInstances - Listing instances with labels",
		zap.String("component", strings.ToLower(req.ComponentName)),
		zap.String("group", req.GroupName))
	err = s.dataplaneCli.List(ctx, instanceObjList, client.InNamespace(req.Namespace), client.MatchingLabels{
		PingcapComponentLabelKey: strings.ToLower(req.ComponentName),
		PingcapGroupLabelKey:     req.GroupName,
	})
	if err != nil {
		logger.Error("DeleteGroupInstances - Failed to list instances", zap.Error(err))
		return nil, status.Error(codes.Internal, "failed to list instances: "+err.Error())
	}

	items, err := meta.ExtractList(instanceObjList)
	if err != nil {
		logger.Error("DeleteGroupInstances - Failed to extract list from instance object list", zap.Error(err))
		return nil, status.Error(codes.Internal, "failed to extract list from instance object list: "+err.Error())
	}

	logger.Info("DeleteGroupInstances - Found instances to delete", zap.Int("count", len(items)))

	// delete instances
	deletedCount := 0
	for i, it := range items {
		instanceObj, ok := it.(client.Object)
		if !ok {
			return nil, status.Error(codes.Internal, "failed to convert runtime instance object to client.Object")
		}

		logger.Info("DeleteGroupInstances - Deleting instance",
			zap.Int("current", i+1),
			zap.Int("total", len(items)),
			zap.String("namespace", instanceObj.GetNamespace()),
			zap.String("name", instanceObj.GetName()))

		err = s.dataplaneCli.Delete(ctx, instanceObj)
		if err != nil {
			logger.Error("DeleteGroupInstances - Failed to delete instance",
				zap.String("namespace", instanceObj.GetNamespace()),
				zap.String("name", instanceObj.GetName()),
				zap.Error(err))
			return nil, status.Error(codes.Internal, "failed to delete instance: "+err.Error())
		}

		deletedCount++
		logger.Info("DeleteGroupInstances - Successfully deleted instance",
			zap.Int("current", i+1),
			zap.Int("total", len(items)),
			zap.String("namespace", instanceObj.GetNamespace()),
			zap.String("name", instanceObj.GetName()))
	}

	logger.Info("DeleteGroupInstances completed successfully",
		zap.String("namespace", req.Namespace),
		zap.String("component_name", req.ComponentName),
		zap.String("group_name", req.GroupName),
		zap.Int("deleted_instances", deletedCount))
	return &emptypb.Empty{}, nil
}

func (s *operatorV2Service) DeleteGroupInstancesAndResumeCluster(ctx context.Context, req *apiv1.DeleteGroupInstancesAndResumeClusterReq) (*emptypb.Empty, error) {
	logger := tilog.WithContextL(ctx).With(
		zap.String("namespace", req.Namespace),
		zap.String("cluster_name", req.ClusterName),
		zap.String("component_name", req.ComponentName),
		zap.String("group_name", req.GroupName),
	)
	logger.Info("DeleteGroupInstancesAndResumeCluster called")

	// validate cluster exists
	logger.Info("DeleteGroupInstancesAndResumeCluster - Validating cluster exists")
	cluster, err := validateOperatorV2Cluster(ctx, s.dataplaneCli, req.Namespace, req.ClusterName)
	if err != nil {
		logger.Error("DeleteGroupInstancesAndResumeCluster - Cluster validation failed", zap.Error(err))
		return nil, err
	}
	// delete group instances
	logger.Info("DeleteGroupInstancesAndResumeCluster - Deleting group instances")
	_, err = s.DeleteGroupInstances(ctx, &apiv1.DeleteGroupInstancesReq{
		Namespace:     req.Namespace,
		ComponentName: req.ComponentName,
		GroupName:     req.GroupName,
	})
	if err != nil {
		logger.Error("DeleteGroupInstancesAndResumeCluster - Failed to delete group instances", zap.Error(err))
		return nil, err
	}

	logger.Info("DeleteGroupInstancesAndResumeCluster - Group instances deleted successfully")

	// resume cluster
	logger.Info("DeleteGroupInstancesAndResumeCluster - Resuming cluster",
		zap.String("namespace", req.Namespace),
		zap.String("cluster_name", req.ClusterName))
	cluster.Spec.Paused = false
	if err := s.dataplaneCli.Update(ctx, cluster); err != nil {
		logger.Error("DeleteGroupInstancesAndResumeCluster - Failed to update cluster", zap.Error(err))
		return nil, status.Error(codes.Internal, "failed to update cluster: "+err.Error())
	}

	logger.Info("DeleteGroupInstancesAndResumeCluster completed successfully")
	return &emptypb.Empty{}, nil
}

func validateOperatorV2Cluster(ctx context.Context, dataplaneCli client.Client, namespace, name string) (*v1alpha1.Cluster, error) {
	if namespace == "" || name == "" {
		return nil, status.Error(codes.InvalidArgument, "namespace and name are required")
	}

	cluster := &v1alpha1.Cluster{}
	if err := dataplaneCli.Get(ctx, client.ObjectKey{
		Namespace: namespace,
		Name:      name,
	}, cluster); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, status.Error(codes.NotFound, "cluster not found")
		}
		return nil, status.Error(codes.Internal, "failed to get cluster: "+err.Error())
	}
	return cluster, nil
}
