package key

import (
	"context"

	"google.golang.org/protobuf/types/known/emptypb"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client"

	dmpbv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/dataplane-manager/v1"
	regionalapiv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/dataplane-manager/v1"
	"github.com/tidbcloud/infra-provider/regional-server/api/v1alpha1"
	bizerr "github.com/tidbcloud/infra-provider/regional-server/pkg/common/errors"
	rsconfig "github.com/tidbcloud/infra-provider/regional-server/pkg/config"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/consts"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/key"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/key/task"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/util"
)

type server struct {
	dmpbv1.UnimplementedKeyServiceServer

	dataPlaneR repository.DataPlaneReader
	appboxR    repository.APPBoxReader
	keyRW      repository.KeyReaderWriter
	keyQueryer key.KeyQueryer
	config     rsconfig.RootConfig
}

func NewService(
	dataPlaneR repository.DataPlaneReader,
	appboxR repository.APPBoxReader,
	keyRW repository.KeyReaderWriter,
	keyQueryer key.KeyQueryer,
	config rsconfig.RootConfig,
) dmpbv1.KeyServiceServer {
	return &server{
		dataPlaneR: dataPlaneR,
		appboxR:    appboxR,
		keyRW:      keyRW,
		keyQueryer: keyQueryer,
		config:     config,
	}
}

func (s *server) CreateKey(ctx context.Context, req *regionalapiv1.CreateKeyRequest) (*regionalapiv1.Key, error) {
	// Check if the DataPlane exists
	dataplane := &v1alpha1.DataPlane{
		ObjectMeta: metav1.ObjectMeta{
			Namespace: s.config.Namespace,
			Name:      req.DataplaneId,
		},
	}
	if err := s.dataPlaneR.Get(ctx, client.ObjectKeyFromObject(dataplane), dataplane); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, bizerr.New(bizerr.ResourceNotFound, "dataplane not found")
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get dataplane error", err)
	}

	labels := map[string]string{
		consts.DataplaneLabelKey:            req.DataplaneId,
		"tags.tidbcloud.com/key-id":         req.Key.Id,
		"tags.tidbcloud.com/cloud-provider": s.config.Provider,
	}
	labels = util.MergeMaps(labels, req.Key.Tags, s.config.ResourceLabels)
	// If KMSConfig specifies the KMS account (e.g., control plane account that owns the
	// KMS instance), use it for the Key CR's AccountRef. Otherwise fallback to the
	// dataplane's own AccountRef (e.g., AWS where KMS keys live in the data plane account).
	accountRef := dataplane.Spec.AccountRef
	if s.config.KMSConfig.AccountID != "" {
		accountRef.AccountID = s.config.KMSConfig.AccountID
		if s.config.ControlPlaneCrossplaneProviderConfig != "" {
			accountRef.ProviderConfigName = s.config.ControlPlaneCrossplaneProviderConfig
		}
	}

	key := &v1alpha1.Key{
		ObjectMeta: metav1.ObjectMeta{
			Name:      req.Key.Id,
			Namespace: s.config.Namespace,
			Labels:    labels,
		},
		Spec: v1alpha1.KeySpec{
			Provider:          s.config.Provider,
			Region:            s.config.RegionConfig.Name,
			TrustedDataplanes: []string{req.DataplaneId},
			AccountRef:        accountRef,
			Tags:              req.Key.Tags,
		},
	}
	if err := s.keyRW.Create(ctx, key); err != nil {
		if apierrors.IsAlreadyExists(err) {
			return nil, bizerr.New(bizerr.ResourceConflict, "key already exists")
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "create key error", err)
	}
	return &regionalapiv1.Key{
		Id:     req.Key.Id,
		Status: regionalapiv1.Key_Creating,
	}, nil
}

func (s *server) GetKey(ctx context.Context, req *regionalapiv1.GetKeyRequest) (*regionalapiv1.Key, error) {
	key := &v1alpha1.Key{
		ObjectMeta: metav1.ObjectMeta{
			Name:      req.KeyId,
			Namespace: s.config.Namespace,
		},
	}
	if err := s.keyRW.Get(ctx, client.ObjectKeyFromObject(key), key); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, bizerr.New(bizerr.ResourceNotFound, "key not found")
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get key error", err)
	}
	return &regionalapiv1.Key{
		Id:         req.KeyId,
		ResourceId: key.Status.ID,
		Tags:       key.Spec.Tags,
		Status:     convertStatus(key.Status),
	}, nil
}

func convertStatus(status v1alpha1.KeyStatus) regionalapiv1.Key_Status {
	switch status.Phase {
	case v1alpha1.KeyPhaseActive:
		return regionalapiv1.Key_Active
	case v1alpha1.KeyPhaseDeleting:
		return regionalapiv1.Key_Deleting
	case v1alpha1.KeyPhaseFailed:
		return regionalapiv1.Key_Failed
	}
	return regionalapiv1.Key_Creating
}

func (s *server) ListKeys(ctx context.Context, req *regionalapiv1.ListKeysRequest) (*regionalapiv1.ListKeysResponse, error) {
	keyList := &v1alpha1.KeyList{}
	listOpts := &client.ListOptions{
		LabelSelector: labels.SelectorFromSet(map[string]string{
			consts.DataplaneLabelKey: req.DataplaneId,
		}),
	}
	err := s.keyRW.List(ctx, keyList, listOpts)
	if err != nil {
		return nil, bizerr.Wrap(bizerr.K8SClientError, "list keys error", err)
	}
	keys := make([]*dmpbv1.Key, 0, len(keyList.Items))
	for _, key := range keyList.Items {
		keys = append(keys, &dmpbv1.Key{
			Id:         key.Name,
			ResourceId: key.Status.ID,
			Status:     convertStatus(key.Status),
		})
	}
	return &dmpbv1.ListKeysResponse{Keys: keys}, nil
}

func (s *server) DeleteKey(ctx context.Context, req *regionalapiv1.DeleteKeyRequest) (*emptypb.Empty, error) {
	key := &v1alpha1.Key{}
	if err := s.keyRW.Get(ctx, types.NamespacedName{
		Namespace: s.config.Namespace,
		Name:      req.KeyId,
	}, key); err != nil {
		if apierrors.IsNotFound(err) {
			return &emptypb.Empty{}, nil
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get key error", err)
	}

	if err := s.keyRW.Delete(ctx, key); err != nil {
		return nil, bizerr.Wrap(bizerr.K8SClientError, "delete key error", err)
	}
	return &emptypb.Empty{}, nil
}

func (s *server) AddTrustedDataplane(ctx context.Context, req *regionalapiv1.AddTrustedDataplaneRequest) (*emptypb.Empty, error) {
	// Get the key
	key := &v1alpha1.Key{}
	if err := s.keyRW.Get(ctx, types.NamespacedName{
		Namespace: s.config.Namespace,
		Name:      req.KeyId,
	}, key); err != nil {
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get key error", err)
	}

	// Check if the role is already trusted
	for _, role := range key.Spec.TrustedDataplanes {
		if role == req.TrustedDataplaneId {
			return &emptypb.Empty{}, nil // Already exists, no-op
		}
	}

	dataplane := &v1alpha1.DataPlane{}
	if err := s.dataPlaneR.Get(ctx, client.ObjectKey{
		Namespace: s.config.Namespace,
		Name:      req.TrustedDataplaneId,
	}, dataplane); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, bizerr.New(bizerr.ResourceNotFound, "trusted dataplane not found")
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get trusted dataplane error", err)
	}
	if dataplane.Status.Phase == v1alpha1.DataPlanePhaseDeleting {
		return nil, bizerr.New(bizerr.ResourceNotReady, "trusted dataplane is being deleted")
	}

	// Add the trusted role
	key.Spec.TrustedDataplanes = append(key.Spec.TrustedDataplanes, req.TrustedDataplaneId)
	err := s.keyRW.Update(ctx, key)
	if err != nil {
		return nil, bizerr.Wrap(bizerr.K8SClientError, "update key error", err)
	}

	return &emptypb.Empty{}, nil
}

func (s *server) RemoveTrustedDataplane(ctx context.Context, req *regionalapiv1.RemoveTrustedDataplaneRequest) (*emptypb.Empty, error) {
	// Get the key
	key := &v1alpha1.Key{}
	if err := s.keyRW.Get(ctx, types.NamespacedName{
		Namespace: s.config.Namespace,
		Name:      req.KeyId,
	}, key); err != nil {
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get key error", err)
	}

	// Find and remove the trusted role
	roleFound := false
	var newTrustedRoles []string
	for _, role := range key.Spec.TrustedDataplanes {
		if role != req.TrustedDataplaneId {
			newTrustedRoles = append(newTrustedRoles, role)
		} else {
			roleFound = true
		}
	}

	if !roleFound {
		return &emptypb.Empty{}, nil // Role not found, no-op
	}

	// Update the key with the new trusted roles list
	key.Spec.TrustedDataplanes = newTrustedRoles
	err := s.keyRW.Update(ctx, key)
	if err != nil {
		return nil, bizerr.Wrap(bizerr.K8SClientError, "update key error", err)
	}

	return &emptypb.Empty{}, nil
}

func (s *server) GetTrustedDataplane(ctx context.Context, req *regionalapiv1.GetTrustedDataplaneRequest) (*regionalapiv1.GetTrustedDataplaneResponse, error) {
	status, err := s.keyQueryer.GetTrustedDataplaneStatus(ctx, s.config.Namespace, req.KeyId, req.TrustedDataplaneId)
	if err != nil {
		return nil, bizerr.Wrap(bizerr.K8SClientError, "get key trusted dataplane status error", err)
	}
	return convertTrustedDataplaneStatus(status), nil
}

func convertTrustedDataplaneStatus(status *task.KeyTrustedDataplaneStatus) *regionalapiv1.GetTrustedDataplaneResponse {
	switch *status {
	case task.KeyTrustedDataplaneStatusNotExists:
		return &regionalapiv1.GetTrustedDataplaneResponse{
			Status: regionalapiv1.GetTrustedDataplaneResponse_NotExists,
		}
	case task.KeyTrustedDataplaneStatusPending:
		return &regionalapiv1.GetTrustedDataplaneResponse{
			Status: regionalapiv1.GetTrustedDataplaneResponse_Accepted,
		}
	case task.KeyTrustedDataplaneStatusAccepted:
		return &regionalapiv1.GetTrustedDataplaneResponse{
			Status: regionalapiv1.GetTrustedDataplaneResponse_Accepted,
		}
	case task.KeyTrustedDataplaneStatusSynced:
		return &regionalapiv1.GetTrustedDataplaneResponse{
			Status: regionalapiv1.GetTrustedDataplaneResponse_Synced,
		}
	default:
		return &regionalapiv1.GetTrustedDataplaneResponse{
			Status: regionalapiv1.GetTrustedDataplaneResponse_TrustedDataplane_Unspecified,
		}
	}
}
