package cache

import (
	"fmt"

	"k8s.io/apimachinery/pkg/types"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

	"github.com/argoproj/gitops-engine/pkg/utils/kube"
)

// Resource holds the information about Kubernetes resource, ownership references and optional information
type Resource struct {
	// ResourceVersion holds most recent observed resource version
	ResourceVersion string
	// Resource reference
	Ref corev1.ObjectReference
	// References to resource owners
	OwnerRefs []metav1.OwnerReference
	// Optional creation timestamp of the resource
	CreationTimestamp *metav1.Time
	// Optional additional information about the resource
	Info any
	// Optional whole resource manifest
	Resource *unstructured.Unstructured

	// answers if resource is inferred parent of provided resource
	isInferredParentOf func(key kube.ResourceKey) bool
}

func (r *Resource) ResourceKey() kube.ResourceKey {
	return kube.NewResourceKey(r.Ref.GroupVersionKind().Group, r.Ref.Kind, r.Ref.Namespace, r.Ref.Name)
}

func (r *Resource) isParentOf(child *Resource) bool {
	for i, ownerRef := range child.OwnerRefs {
		// backfill UID of inferred owner child references
		if ownerRef.UID == "" && r.Ref.Kind == ownerRef.Kind && r.Ref.APIVersion == ownerRef.APIVersion && r.Ref.Name == ownerRef.Name {
			ownerRef.UID = r.Ref.UID
			child.OwnerRefs[i] = ownerRef
			return true
		}

		if r.Ref.UID == ownerRef.UID {
			return true
		}
	}

	return false
}

// setOwnerRef adds or removes specified owner reference
func (r *Resource) setOwnerRef(ref metav1.OwnerReference, add bool) {
	index := -1
	for i, item := range r.OwnerRefs {
		if item.UID == ref.UID {
			index = i
			break
		}
	}
	added := index > -1
	if add != added {
		if add {
			r.OwnerRefs = append(r.OwnerRefs, ref)
		} else {
			r.OwnerRefs = append(r.OwnerRefs[:index], r.OwnerRefs[index+1:]...)
		}
	}
}

func (r *Resource) toOwnerRef() metav1.OwnerReference {
	return metav1.OwnerReference{UID: r.Ref.UID, Name: r.Ref.Name, Kind: r.Ref.Kind, APIVersion: r.Ref.APIVersion}
}

// iterateChildrenV2 is a depth-first traversal of the graph of resources starting from the current resource.
func (r *Resource) iterateChildrenV2(graph map[kube.ResourceKey]map[types.UID]*Resource, ns map[kube.ResourceKey]*Resource, visited map[kube.ResourceKey]int, action func(err error, child *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool) {
	key := r.ResourceKey()
	if visited[key] == 2 {
		return
	}
	// this indicates that we've started processing this node's children
	visited[key] = 1
	defer func() {
		// this indicates that we've finished processing this node's children
		visited[key] = 2
	}()
	children, ok := graph[key]
	if !ok || children == nil {
		return
	}
	for _, child := range children {
		childKey := child.ResourceKey()
		// For cross-namespace relationships, child might not be in ns, so use it directly from graph
		switch visited[childKey] {
		case 1:
			// Since we encountered a node that we're currently processing, we know we have a circular dependency.
			_ = action(fmt.Errorf("circular dependency detected. %s is child and parent of %s", childKey.String(), key.String()), child, ns)
		case 0:
			if action(nil, child, ns) {
				child.iterateChildrenV2(graph, ns, visited, action)
			}
		}
	}
}
