package service

import (
	"context"
	"fmt"

	certmanagerv1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	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"
	"github.com/tidbcloud/infra-provider/pkg/cert"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

type CertService struct {
	apiv1.UnimplementedCertServiceServer

	dataplaneCli client.Client
}

func NewCertService(cli client.Client) *CertService {
	cs := &CertService{dataplaneCli: cli}
	return cs
}

func (s *CertService) getTCByClusterId(ctx context.Context, clusterId, tcName string) (*v1alpha1.TidbCluster, error) {
	ns := buildNamespace(clusterId)

	var tc v1alpha1.TidbCluster
	if err := s.dataplaneCli.Get(ctx, client.ObjectKey{Namespace: ns, Name: tcName}, &tc); err != nil {
		return nil, fmt.Errorf("failed to get tidbcluster CR(%s): %w", tcName, err)
	}
	return &tc, nil
}

func (s *CertService) CreateCertificate(ctx context.Context, req *apiv1.CreateCertificateReq) (*apicommon.Certificate, error) {
	certificate, err := s.buildCert(ctx, req)
	if err != nil {
		return nil, err
	}

	if err := s.dataplaneCli.Create(ctx, certificate); err != nil {
		if apierrors.IsAlreadyExists(err) {
			return nil, status.Errorf(codes.AlreadyExists, "certificate already exists: %s", certificate.Name)
		}

		return nil, status.Errorf(codes.Internal, "failed to create certificate: %s", err)
	}

	return &apicommon.Certificate{
		Name: certificate.Name,
	}, nil
}

func (s *CertService) buildCert(ctx context.Context, req *apiv1.CreateCertificateReq) (*certmanagerv1.Certificate, error) {
	mainTC, err := s.getTCByClusterId(ctx, req.ClusterId, tcName)
	if err != nil {
		return nil, err
	}
	// read feature flags from main TC
	useSelfSignedClusterComponentCert := FeatureEnabled(mainTC, consts.ClusterFeatureUseSelfSignedCertForTiDBComponents)

	ns := buildNamespace(req.ClusterId)
	name := namer.NewNamer().
		WithTiDBNamespace(ns).
		WithTiDBName(req.TicdcTcName) // build cert name with the requested TC name

	switch req.Kind {
	case apicommon.DataplaneCertKind_ClusterClientTLSCert:
		return cert.ClusterClientCert(name, useSelfSignedClusterComponentCert), nil

	case apicommon.DataplaneCertKind_ClusterTLSCert:
		if req.ComponentType == nil {
			return nil, status.Errorf(codes.InvalidArgument, "component type is required for %s", req.Kind)
		}
		name = name.WithComponent(convertComponentTypeFromAPI(req.GetComponentType()))
		return cert.ComponentCert(name, useSelfSignedClusterComponentCert), nil
	default:
		return nil, status.Errorf(codes.InvalidArgument, "invalid cert kind: %s", req.Kind)
	}
}

const (
	AnnoKeyFeatureNamePrefix = "tidbcloud.com/use-"
)

func FeatureEnabled(obj metav1.Object, featureName string) bool {
	return obj.GetAnnotations()[AnnoKeyFeatureNamePrefix+featureName] == "true"
}
