package e2e

import (
	"context"
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"

	capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2"
	"github.com/projectcapsule/capsule/pkg/api/meta"
	"github.com/projectcapsule/capsule/pkg/api/rbac"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ = Describe("NamespaceStatus objects", Ordered, Label("tenant", "rules", "status"), func() {
	ctx := context.Background()

	// Two tenants, each with one owner (reuse your existing ownerClient/NamespaceCreation helpers)
	tntA := &capsulev1beta2.Tenant{
		ObjectMeta: metav1.ObjectMeta{
			Name: "e2e-rule-status-a",
			Labels: map[string]string{
				"env": "e2e",
			},
		},
		Spec: capsulev1beta2.TenantSpec{
			Owners: rbac.OwnerListSpec{
				{
					CoreOwnerSpec: rbac.CoreOwnerSpec{
						UserSpec: rbac.UserSpec{Name: "e2e-rule-status-a", Kind: "User"},
					},
				},
			},
		},
	}

	tntB := &capsulev1beta2.Tenant{
		ObjectMeta: metav1.ObjectMeta{
			Name: "e2e-rule-status-b",
			Labels: map[string]string{
				"env": "e2e",
			},
		},
		Spec: capsulev1beta2.TenantSpec{
			Owners: rbac.OwnerListSpec{
				{
					CoreOwnerSpec: rbac.CoreOwnerSpec{
						UserSpec: rbac.UserSpec{Name: "e2e-rule-status-b", Kind: "User"},
					},
				},
			},
		},
	}

	var (
		nsA1 *corev1.Namespace
		nsA2 *corev1.Namespace
		nsB1 *corev1.Namespace
	)

	JustBeforeEach(func() {
		// Create tenants
		EventuallyCreation(func() error {
			tntA.ResourceVersion = ""
			return k8sClient.Create(ctx, tntA)
		}).Should(Succeed())

		TenantReady(tntA, metav1.ConditionTrue, defaultTimeoutInterval)

		EventuallyCreation(func() error {
			tntB.ResourceVersion = ""
			return k8sClient.Create(ctx, tntB)
		}).Should(Succeed())

		TenantReady(tntB, metav1.ConditionTrue, defaultTimeoutInterval)

		nsA1 = NewNamespace("e2e-rule-status-a-1", map[string]string{
			meta.TenantLabel: tntA.GetName(),
		})
		nsA2 = NewNamespace("e2e-rule-status-a-2", map[string]string{
			meta.TenantLabel: tntA.GetName(),
		})
		nsB1 = NewNamespace("e2e-rule-status-b-1", map[string]string{
			meta.TenantLabel: tntB.GetName(),
		})

		NamespaceCreation(nsA1, tntA.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed())
		NamespaceCreation(nsA2, tntA.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed())
		NamespaceCreation(nsB1, tntB.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed())

		// Wait until tenants list their namespaces (optional but makes debugging easier)
		NamespaceIsPartOfTenant(tntA, nsA1).Should(Succeed())
		NamespaceIsPartOfTenant(tntA, nsA2).Should(Succeed())
		NamespaceIsPartOfTenant(tntB, nsB1).Should(Succeed())
	})

	JustAfterEach(func() {
		// Best-effort cleanup namespaces first (your env may already handle this)
		for _, n := range []*corev1.Namespace{nsA1, nsA2, nsB1} {
			if n == nil {
				continue
			}
			_ = k8sClient.Delete(ctx, n)
		}

		// Delete tenants
		if tntA != nil {
			EventuallyDeletion(tntA)
		}
		if tntB != nil {
			EventuallyDeletion(tntB)
		}
	})

	expectNamespaceStatusFor := func(ns *corev1.Namespace, tenant *capsulev1beta2.Tenant) {
		By(fmt.Sprintf("verifying NamespaceStatus for namespace %q (tenant=%q)", ns.Name, tenant.GetName()))

		Eventually(func(g Gomega) {
			// Re-read namespace to get UID reliably (in case local object is stale)
			curNS := &corev1.Namespace{}
			g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: ns.Name}, curNS)).To(Succeed())

			nsStatus := &capsulev1beta2.RuleStatus{}
			g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: meta.NameForManagedRuleStatus(), Namespace: ns.Name}, nsStatus)).To(Succeed())

			// 2) OwnerReference must point to the Namespace and be controller owner
			g.Expect(nsStatus.OwnerReferences).NotTo(BeEmpty())

			var found bool
			for _, or := range nsStatus.OwnerReferences {
				if or.APIVersion == capsulev1beta2.GroupVersion.String() &&
					or.Kind == "Tenant" &&
					or.Name == tenant.Name &&
					or.UID == tenant.UID {

					found = true

					break
				}
			}
			g.Expect(found).To(BeTrue(), "expected NamespaceStatus to have Tenant controller OwnerReference")
		}, defaultTimeoutInterval, defaultPollInterval).Should(Succeed())
	}

	It("creates one NamespaceStatus per namespace, with correct Status.Tenant and Namespace controller OwnerReference", func() {
		getTenantA := &capsulev1beta2.Tenant{}
		Expect(k8sClient.Get(ctx, client.ObjectKey{Name: tntA.GetName()}, getTenantA)).To(Succeed())

		getTenantB := &capsulev1beta2.Tenant{}
		Expect(k8sClient.Get(ctx, client.ObjectKey{Name: tntB.GetName()}, getTenantB)).To(Succeed())

		expectNamespaceStatusFor(nsA1, getTenantA)
		expectNamespaceStatusFor(nsA2, getTenantA)
		expectNamespaceStatusFor(nsB1, getTenantB)
	})

	It("removes NamespaceStatus when the Namespace is deleted (ownerReference GC)", func() {
		getTenantA := &capsulev1beta2.Tenant{}
		Expect(k8sClient.Get(ctx, client.ObjectKey{Name: tntA.GetName()}, getTenantA)).To(Succeed())

		// Ensure it exists first
		expectNamespaceStatusFor(nsA1, getTenantA)

		// Delete namespace
		Expect(k8sClient.Delete(ctx, nsA1)).To(Succeed())

		// Namespace deletion can take time; once it's gone, the status should be GC'd
		Eventually(func() bool {
			// confirm namespace gone or terminating; either way, check status disappears eventually
			nsStatus := &capsulev1beta2.RuleStatus{}
			err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.NameForManagedRuleStatus(), Namespace: nsA1.Name}, nsStatus)
			return apierrors.IsNotFound(err)
		}, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue())
	})
})
