package watch_graph //nolint:staticcheck

import (
	"context"
	"unique"

	"github.com/google/cel-go/cel"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/apimachinery/pkg/watch"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/util/jsonpath"
)

type ArcType byte // enough bits to represent each arc type as an individual bit.

const (
	UnknownArcType        ArcType = 0 // must be zero
	OwnerReferenceArcType ArcType = 1 << (iota - 1)
	ReferenceArcType
	TransitiveReferenceArcType
)

func (at ArcType) String() string {
	switch at { //nolint:exhaustive
	case OwnerReferenceArcType:
		return "or"
	case ReferenceArcType:
		return "r"
	case TransitiveReferenceArcType:
		return "t"
	default:
		return "unknown"
	}
}

func ParseArcTypeStr(at string) ArcType {
	switch at {
	case "or":
		return OwnerReferenceArcType
	case "r":
		return ReferenceArcType
	case "t":
		return TransitiveReferenceArcType
	default:
		return UnknownArcType
	}
}

// ArcTypeSet is a set of arc types.
type ArcTypeSet ArcType

func (s *ArcTypeSet) Add(v ArcType) {
	*s |= ArcTypeSet(v)
}

func (s *ArcTypeSet) Remove(v ArcType) {
	*s &= ^ArcTypeSet(v)
}

func (s *ArcTypeSet) Contains(v ArcType) bool {
	return (*s & ArcTypeSet(v)) != 0
}

func (s *ArcTypeSet) IsEmpty() bool {
	return *s == 0
}

type gkInfo struct {
	gvr        unique.Handle[schema.GroupVersionResource]
	namespaced bool
}

type gvrInfo struct {
	object     QueryIncludeObject
	namespaced bool
}

type gvrInfos struct {
	infos map[unique.Handle[schema.GroupVersionResource]]gvrInfo

	// Our little simple replacement for the rest mapper for selected resources.
	gk2info map[schema.GroupKind]gkInfo

	// All group-kinds that we got from discovery.
	// Includes non-watchable resources.
	// Does not include sub-resources.
	gks sets.Set[schema.GroupKind]

	// Is discovery info partial or complete.
	// Partial info may not contain all GVRs because e.g. one of the configured aggregated API servers is offline
	// and hence discovery info for the types it exposes is unavailable at the moment.
	// See https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/apiserver-aggregation/.
	isPartial bool
}

// QueryIncludeObject holds config for object filtering.
type QueryIncludeObject struct {
	LabelSelector            string
	FieldSelector            string
	ObjectSelectorExpression cel.Program        // can be nil
	JSONPath                 *jsonpath.JSONPath // can be nil
}

type QueryInclude struct {
	ResourceSelectorExpression cel.Program // never nil

	Object QueryIncludeObject
}

type QueryExclude struct {
	ResourceSelectorExpression cel.Program // never nil
}

type Namespaces struct {
	Names                    sets.Set[string]
	LabelSelector            string
	FieldSelector            string
	ObjectSelectorExpression cel.Program // can be nil
}

// All checks if all namespaces should be watched.
func (n *Namespaces) All() bool {
	return n.Names.Len() == 0 && n.LabelSelector == "" && n.ObjectSelectorExpression == nil
}

func (n *Namespaces) isFixedList() bool {
	return n.Names.Len() > 0
}

type selectedGVR struct {
	inf  cache.SharedIndexInformer
	stop context.CancelFunc
	wait func()
}

type nsStatus byte

const (
	nsDeleted nsStatus = iota // must be zero
	nsSelected
	nsFilteredOut
)

type nsWithStatus struct {
	name   string
	status nsStatus
}

type VertexID struct {
	GVR       unique.Handle[schema.GroupVersionResource]
	Namespace string
	Name      string
}

type VertexData struct {
	Object      map[string]any     // do not mutate the contents of the map as this is the data from informer, not a copy.
	JSONPath    *jsonpath.JSONPath // nil if no JSON Path processing is necessary
	HelmRelease []byte
}

type ArcAttrs struct {
	Controller              bool `json:"c,omitempty"`
	BlockOwnerDeletion      bool `json:"b,omitempty"`
	DestinationDoesNotExist bool `json:"e,omitempty"`
}

func (a ArcAttrs) IsZero() bool {
	var zero ArcAttrs
	return a == zero
}

func newInformer(client dynamic.Interface, gvr schema.GroupVersionResource, namespace, labelSelector, fieldSelector string) cache.SharedIndexInformer {
	nsClient := client.Resource(gvr).Namespace(namespace)
	return cache.NewSharedIndexInformerWithOptions(
		&cache.ListWatch{
			ListWithContextFunc: func(ctx context.Context, opts metav1.ListOptions) (runtime.Object, error) {
				opts.LabelSelector = labelSelector
				opts.FieldSelector = fieldSelector
				return nsClient.List(context.WithoutCancel(ctx), opts) // Watch will be closed via Stop()
			},
			WatchFuncWithContext: func(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) {
				opts.LabelSelector = labelSelector
				opts.FieldSelector = fieldSelector
				return nsClient.Watch(context.WithoutCancel(ctx), opts) // Watch will be closed via Stop()
			},
		},
		&unstructured.Unstructured{},
		cache.SharedIndexInformerOptions{
			ObjectDescription: gvr.String(),
		},
	)
}
