package k8sresource

import (
	"context"
	"strings"

	regionalpb "github.com/tidbcloud/infra-provider/api/tidbcloud/regional-server/v1"
	"k8s.io/apimachinery/pkg/api/meta"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/cli-runtime/pkg/printers"
	"k8s.io/client-go/rest"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

type PlaneContext struct {
	Ctx        context.Context
	Client     client.Reader
	APIReader  client.Reader
	RestConfig *rest.Config
	Mapper     meta.RESTMapper
}

func listPlane(pc PlaneContext, req *regionalpb.ListK8SResourcesRequest) (*regionalpb.ListK8SResourcesResponse, error) {
	gvk, err := ResolveGVK(pc.Mapper, req.Gvk)
	if err != nil {
		return nil, err
	}
	if err := CheckGKAllowed(gvk); err != nil {
		return nil, err
	}
	if err := CheckQueryPolicy(pc.Mapper, gvk, req); err != nil {
		return nil, err
	}
	if isDetail(req.Option) {
		return listPlaneDetail(pc, gvk, req)
	}
	return listPlaneSummary(pc, gvk, req)
}

func listPlaneDetail(pc PlaneContext, gvk schema.GroupVersionKind, req *regionalpb.ListK8SResourcesRequest) (*regionalpb.ListK8SResourcesResponse, error) {
	_, _, byName := namespacedName(req.Selector)
	if byName {
		return listPlaneDetailByName(pc, gvk, req)
	}
	return listPlaneDetailByList(pc, gvk, req)
}

func listPlaneDetailByName(pc PlaneContext, gvk schema.GroupVersionKind, req *regionalpb.ListK8SResourcesRequest) (*regionalpb.ListK8SResourcesResponse, error) {
	withEvents := req.Option != nil && req.Option.Events
	ns, name, _ := namespacedName(req.Selector)
	obj, err := getUnstructured(pc.Ctx, pc.Client, gvk, ns, name)
	if err != nil {
		return nil, err
	}

	var content string
	var hints string
	if withEvents {
		wrappers, failed := attachEventsToResources(pc.Ctx, pc.APIReader, gvk, []unstructured.Unstructured{*obj})
		SanitizeResourcesWithEventsForDetail(gvk, wrappers)
		content, err = marshalDetailWithEventsYAML(&wrappers[0], nil)
		hints = appendEventFetchHints("", failed, 1)
	} else {
		SanitizeObjectForDetail(gvk, obj)
		content, err = marshalDetailYAML(obj, nil, gvk)
	}
	if err != nil {
		return nil, err
	}
	return detailYAMLResponse(content, 1, false, hints), nil
}

func listPlaneDetailByList(pc PlaneContext, gvk schema.GroupVersionKind, req *regionalpb.ListK8SResourcesRequest) (*regionalpb.ListK8SResourcesResponse, error) {
	withEvents := req.Option != nil && req.Option.Events
	limit, err := resolveListLimit(req.Option, true)
	if err != nil {
		return nil, err
	}
	list, err := listUnstructured(pc.Ctx, pc.Client, gvk, buildListOptions(req.Selector, limit)...)
	if err != nil {
		return nil, err
	}
	listedCount := len(list.Items)
	withFieldFilter := hasFieldFilters(req.Selector)
	if withFieldFilter {
		list.Items = filterUnstructuredItems(list.Items, req.Selector.Field)
	}

	var content string
	var eventFailed int
	if withEvents {
		var wrappers []resourceWithEvents
		wrappers, eventFailed = attachEventsToResources(pc.Ctx, pc.APIReader, gvk, list.Items)
		SanitizeResourcesWithEventsForDetail(gvk, wrappers)
		content, err = marshalDetailWithEventsYAML(nil, wrappers)
	} else {
		SanitizeListForDetail(gvk, list)
		content, err = marshalDetailYAML(nil, list, gvk)
	}
	if err != nil {
		return nil, err
	}

	truncated, hints := resolveTruncatedWithHintsFromList(list, limit, true, withFieldFilter)
	hints = appendFieldFilterHints(hints, listedCount, len(list.Items))
	if withEvents {
		hints = appendEventFetchHints(hints, eventFailed, len(list.Items))
	}
	return detailYAMLResponse(content, int32(len(list.Items)), truncated, hints), nil
}

func listPlaneSummary(pc PlaneContext, gvk schema.GroupVersionKind, req *regionalpb.ListK8SResourcesRequest) (*regionalpb.ListK8SResourcesResponse, error) {
	_, _, byName := namespacedName(req.Selector)
	if byName {
		return listPlaneSummaryByName(pc, gvk, req)
	}
	return listPlaneSummaryByList(pc, gvk, req)
}

func listPlaneSummaryByName(pc PlaneContext, gvk schema.GroupVersionKind, req *regionalpb.ListK8SResourcesRequest) (*regionalpb.ListK8SResourcesResponse, error) {
	ns, name, _ := namespacedName(req.Selector)
	table, err := getAsTable(pc.Ctx, pc.RestConfig, pc.Mapper, gvk, ns, name)
	if err != nil {
		return nil, err
	}

	namespaced, err := resolveNamespaced(pc.Mapper, gvk)
	if err != nil {
		return nil, err
	}
	printOpts := summaryPrintOptions(namespaced, req.Selector, true)
	content, err := marshalSummaryText(table, printOpts)
	if err != nil {
		return nil, err
	}
	return summaryTextResponse(content, int32(len(table.Rows)), false, ""), nil
}

func listPlaneSummaryByList(pc PlaneContext, gvk schema.GroupVersionKind, req *regionalpb.ListK8SResourcesRequest) (*regionalpb.ListK8SResourcesResponse, error) {
	limit, err := resolveListLimit(req.Option, false)
	if err != nil {
		return nil, err
	}
	table, err := listAsTable(pc.Ctx, pc.RestConfig, pc.Mapper, gvk, req.Selector, limit)
	if err != nil {
		return nil, err
	}

	listedCount := len(table.Rows)
	withFieldFilter := hasFieldFilters(req.Selector)
	if withFieldFilter {
		table.Rows, err = filterTableRows(table.Rows, req.Selector.Field)
		if err != nil {
			return nil, err
		}
	}

	namespaced, err := resolveNamespaced(pc.Mapper, gvk)
	if err != nil {
		return nil, err
	}
	printOpts := summaryPrintOptions(namespaced, req.Selector, false)
	content, err := marshalSummaryText(table, printOpts)
	if err != nil {
		return nil, err
	}

	truncated, hints := resolveTruncatedWithHintsFromList(table, limit, false, withFieldFilter)
	hints = appendFieldFilterHints(hints, listedCount, len(table.Rows))
	return summaryTextResponse(content, int32(len(table.Rows)), truncated, hints), nil
}

func summaryPrintOptions(namespaced bool, sel *regionalpb.Selector, byName bool) printers.PrintOptions {
	opts := printers.PrintOptions{Wide: true}
	if namespaced && !byName && strings.TrimSpace(sel.GetNamespace()) == "" {
		opts.WithNamespace = true
	}
	return opts
}
