package k8sresource

import (
	"context"
	"fmt"
	"strings"

	regionalpb "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/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/common/utils"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func (s *Service) listDataPlane(ctx context.Context, req *regionalpb.ListK8SResourcesRequest) (*regionalpb.ListK8SResourcesResponse, error) {
	appbox, err := s.resolveAPPBox(ctx, req.DataPlane)
	if err != nil {
		return nil, err
	}
	pc, err := s.buildDPPlaneContext(ctx, appbox)
	if err != nil {
		return nil, err
	}
	return listPlane(pc, req)
}

func (s *Service) resolveAPPBox(ctx context.Context, dp *regionalpb.DataPlane) (*v1alpha1.APPBox, error) {
	projectID := strings.TrimSpace(dp.GetProjectId())
	clusterID := strings.TrimSpace(dp.GetClusterId())

	if projectID != "" {
		return s.resolveAPPBoxByProject(ctx, projectID)
	}
	return s.resolveAPPBoxByCluster(ctx, clusterID)
}

func (s *Service) resolveAPPBoxByProject(ctx context.Context, projectID string) (*v1alpha1.APPBox, error) {
	list := &v1alpha1.APPBoxList{}
	if err := s.cpClient.List(ctx, list, client.MatchingLabels{
		utils.GenProjectLabelKey(projectID): "true",
	}); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, err.Error())
	}
	return pickAPPBox(list.Items, fmt.Sprintf("project_id %q", projectID))
}

func (s *Service) resolveAPPBoxByCluster(ctx context.Context, clusterID string) (*v1alpha1.APPBox, error) {
	routes := &v1alpha1.APPRouteList{}
	if err := s.cpClient.List(ctx, routes, client.MatchingLabels{
		utils.GenClusterLabelKey(clusterID): "true",
	}); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, err.Error())
	}
	if len(routes.Items) == 0 {
		return nil, bizerr.New(bizerr.ResourceNotFound, fmt.Sprintf("no approute found for cluster_id %q", clusterID))
	}
	if len(routes.Items) > 1 {
		return nil, bizerr.New(bizerr.ParameterInvalid, fmt.Sprintf("multiple approutes found for cluster_id %q", clusterID))
	}
	route := routes.Items[0]
	box := &v1alpha1.APPBox{}
	if err := s.cpClient.Get(ctx, client.ObjectKey{
		Namespace: route.Spec.APPBoxRef.Namespace,
		Name:      route.Spec.APPBoxRef.Name,
	}, box); err != nil {
		return nil, bizerr.New(bizerr.K8SClientError, err.Error())
	}
	return box, nil
}

func pickAPPBox(items []v1alpha1.APPBox, scope string) (*v1alpha1.APPBox, error) {
	if len(items) == 0 {
		return nil, bizerr.New(bizerr.ResourceNotFound, fmt.Sprintf("no appbox found for %s", scope))
	}
	if len(items) == 1 {
		return &items[0], nil
	}

	active := filterAPPBoxesByPhase(items, v1alpha1.APPBoxPhaseActive)
	if len(active) == 1 {
		return &active[0], nil
	}
	if len(active) > 1 {
		return nil, bizerr.New(bizerr.ParameterInvalid, fmt.Sprintf("multiple active appboxes found for %s", scope))
	}

	readyForDelete := filterAPPBoxesByPhase(items, v1alpha1.APPBoxPhaseReadyForDelete)
	if len(readyForDelete) == 1 {
		return &readyForDelete[0], nil
	}
	return nil, bizerr.New(bizerr.ParameterInvalid, fmt.Sprintf("multiple appboxes found for %s", scope))
}

func filterAPPBoxesByPhase(items []v1alpha1.APPBox, phase v1alpha1.APPBoxPhase) []v1alpha1.APPBox {
	out := make([]v1alpha1.APPBox, 0, len(items))
	for _, item := range items {
		if item.Status.Phase == phase {
			out = append(out, item)
		}
	}
	return out
}

func (s *Service) buildDPPlaneContext(ctx context.Context, appbox *v1alpha1.APPBox) (PlaneContext, error) {
	key := client.ObjectKeyFromObject(appbox)

	if err := s.dpManager.Register(ctx, appbox); err != nil {
		return PlaneContext{}, bizerr.New(bizerr.K8SClientError, err.Error())
	}

	clientGetter, err := s.dpManager.ClientGetter(ctx, key)
	if err != nil {
		return PlaneContext{}, bizerr.New(bizerr.K8SClientError, err.Error())
	}
	cli, err := clientGetter.GetClient(ctx)
	if err != nil {
		return PlaneContext{}, bizerr.New(bizerr.K8SClientError, err.Error())
	}

	apiGetter, err := s.dpManager.APIReaderGetter(ctx, key)
	if err != nil {
		return PlaneContext{}, bizerr.New(bizerr.K8SClientError, err.Error())
	}
	apiReader, err := apiGetter.GetAPIReader(ctx)
	if err != nil {
		return PlaneContext{}, bizerr.New(bizerr.K8SClientError, err.Error())
	}

	restCfg, err := s.dpManager.GetRESTConfig(ctx, key)
	if err != nil {
		return PlaneContext{}, bizerr.New(bizerr.K8SClientError, err.Error())
	}

	return PlaneContext{
		Ctx:        ctx,
		Client:     cli,
		APIReader:  apiReader,
		RestConfig: restCfg,
		Mapper:     cli.RESTMapper(),
	}, nil
}
