package proxy

import (
	"context"
	"fmt"

	pb "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"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/repository"
	"google.golang.org/protobuf/types/known/structpb"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
	newDynamicClientForConfig = func(inConfig *rest.Config) (dynamic.Interface, error) {
		return dynamic.NewForConfig(inConfig)
	}
	configFromAPPBox = controllers.ConfigFromAPPBox
)

type DataPlaneK8sServer struct {
	pb.UnimplementedDataPlaneK8SProxyServiceServer

	localCli    client.Client
	dataPlaneRW repository.DataPlaneReaderWriter
	appBoxRW    repository.APPBoxReaderWriter
	namespace   string
}

func NewDataPlaneK8sServer(
	localCli client.Client,
	dataPlaneRW repository.DataPlaneReaderWriter,
	appBoxRW repository.APPBoxReaderWriter,
	namespace string,
) *DataPlaneK8sServer {
	return &DataPlaneK8sServer{
		localCli:    localCli,
		dataPlaneRW: dataPlaneRW,
		appBoxRW:    appBoxRW,
		namespace:   namespace,
	}
}

func (s *DataPlaneK8sServer) ListResources(
	ctx context.Context,
	req *pb.DataPlaneListResourcesRequest,
) (*structpb.Struct, error) {
	dynamicCli, err := s.dynamicClientForDataPlane(ctx, req.GetDataPlaneId())
	if err != nil {
		return nil, err
	}

	gvr := schema.GroupVersionResource{
		Group:    req.Group,
		Version:  req.Version,
		Resource: req.ResourcePlural,
	}

	return listResources(ctx, dynamicCli.Resource(gvr).Namespace(req.Namespace), req.LabelSelector)
}

func (s *DataPlaneK8sServer) GetResource(
	ctx context.Context,
	req *pb.DataPlaneGetResourceRequest,
) (*structpb.Struct, error) {
	dynamicCli, err := s.dynamicClientForDataPlane(ctx, req.GetDataPlaneId())
	if err != nil {
		return nil, err
	}

	gvr := schema.GroupVersionResource{
		Group:    req.Group,
		Version:  req.Version,
		Resource: req.ResourcePlural,
	}

	return getResource(ctx, dynamicCli.Resource(gvr).Namespace(req.Namespace), req.Name)
}

func (s *DataPlaneK8sServer) dynamicClientForDataPlane(
	ctx context.Context,
	dataPlaneID string,
) (dynamic.Interface, error) {
	var dataPlane v1alpha1.DataPlane
	if err := s.dataPlaneRW.Get(ctx, client.ObjectKey{
		Namespace: s.namespace,
		Name:      dataPlaneID,
	}, &dataPlane); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, bizerr.New(bizerr.ResourceNotFound, "dataplane not found")
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "failed to get dataplane", err)
	}

	if dataPlane.Status.AppBox == nil || dataPlane.Status.AppBox.Name == "" {
		return nil, bizerr.New(
			bizerr.ParameterInvalid,
			fmt.Sprintf("dataplane %s does not have appbox reference", dataPlaneID),
		)
	}

	var appBox v1alpha1.APPBox
	if err := s.appBoxRW.Get(ctx, client.ObjectKey{
		Namespace: dataPlane.Status.AppBox.Namespace,
		Name:      dataPlane.Status.AppBox.Name,
	}, &appBox); err != nil {
		if apierrors.IsNotFound(err) {
			return nil, bizerr.New(bizerr.ResourceNotFound, "appbox not found")
		}
		return nil, bizerr.Wrap(bizerr.K8SClientError, "failed to get appbox", err)
	}

	restConfig, err := configFromAPPBox(
		ctx,
		s.localCli,
		&appBox,
		s.localCli.Scheme(),
		&dataPlane.Spec.AccountRef,
	)
	if err != nil {
		return nil, bizerr.Wrap(bizerr.K8SClientError, "failed to build dataplane kubeconfig", err)
	}

	dynamicCli, err := newDynamicClientForConfig(restConfig)
	if err != nil {
		return nil, bizerr.Wrap(bizerr.K8SClientError, "failed to build dataplane dynamic client", err)
	}

	return dynamicCli, nil
}
