package controller

import (
	"context"
	"errors"
	"testing"

	"github.com/sandbox0-ai/sandbox0/manager/pkg/apis/sandbox0/v1alpha1"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
)

func TestOperatorUpdateTemplateStatusUsesReadyIdlePods(t *testing.T) {
	template := &v1alpha1.SandboxTemplate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "template-a",
			Namespace: "default",
		},
		Spec: v1alpha1.SandboxTemplateSpec{
			Pool: v1alpha1.PoolStrategy{
				MinIdle: 2,
				MaxIdle: 5,
			},
		},
	}

	podIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{
		cache.NamespaceIndex: cache.MetaNamespaceIndexFunc,
	})
	for _, pod := range []*corev1.Pod{
		newOperatorTestPod("default", "idle-ready", "template-a", PoolTypeIdle, corev1.PodRunning, corev1.ConditionTrue),
		newOperatorTestPod("default", "idle-not-ready", "template-a", PoolTypeIdle, corev1.PodRunning, corev1.ConditionFalse),
		newOperatorTestPod("default", "idle-pending", "template-a", PoolTypeIdle, corev1.PodPending, corev1.ConditionTrue),
		newOperatorTestPod("default", "active-running", "template-a", PoolTypeActive, corev1.PodRunning, corev1.ConditionFalse),
		newOperatorTestPod("default", "active-pending", "template-a", PoolTypeActive, corev1.PodPending, corev1.ConditionTrue),
	} {
		require.NoError(t, podIndexer.Add(pod))
	}

	publisher := &recordingTemplateStatsPublisher{}
	op := &Operator{
		podLister:      corelisters.NewPodLister(podIndexer),
		logger:         zap.NewNop(),
		statsPublisher: publisher,
		lastStats:      make(map[string]TemplateCounts),
	}

	needsProbeRequeue, err := op.updateTemplateStatus(context.Background(), template)
	require.NoError(t, err)
	assert.False(t, needsProbeRequeue)

	assert.Equal(t, int32(1), template.Status.IdleCount)
	assert.Equal(t, int32(1), template.Status.ActiveCount)
	require.Len(t, template.Status.Conditions, 2)
	assert.Equal(t, v1alpha1.ConditionFalse, template.Status.Conditions[0].Status)
	assert.Equal(t, "InsufficientIdlePods", template.Status.Conditions[0].Reason)
	assert.Equal(t, v1alpha1.ConditionTrue, template.Status.Conditions[1].Status)
	assert.Equal(t, "PoolHealthy", template.Status.Conditions[1].Reason)

	assert.Equal(t, 1, publisher.calls)
	assert.Equal(t, int32(1), publisher.idleCount)
	assert.Equal(t, int32(1), publisher.activeCount)
	assert.Equal(t, "default/template-a", publisher.statsKey)
	assert.Equal(t, TemplateCounts{IdleCount: 1, ActiveCount: 1}, op.lastStats["default/template-a"])
}

func TestShouldRequeueSandboxProbe(t *testing.T) {
	pod := newOperatorTestPod("default", "idle-not-ready", "template-a", PoolTypeIdle, corev1.PodRunning, corev1.ConditionTrue)
	pod.Spec.ReadinessGates = []corev1.PodReadinessGate{{
		ConditionType: v1alpha1.SandboxPodReadinessConditionType,
	}}
	pod.Status.Conditions = append(pod.Status.Conditions, corev1.PodCondition{
		Type:   v1alpha1.SandboxPodReadinessConditionType,
		Status: corev1.ConditionFalse,
		Reason: "InitialDelay",
	})

	assert.True(t, shouldRequeueSandboxProbe(pod))

	pod.Status.Conditions = []corev1.PodCondition{
		{
			Type:   corev1.PodReady,
			Status: corev1.ConditionTrue,
		},
		{
			Type:   v1alpha1.SandboxPodReadinessConditionType,
			Status: corev1.ConditionTrue,
		},
	}
	assert.False(t, shouldRequeueSandboxProbe(pod))
}

type recordingNamespacePolicyReconciler struct {
	calls []string
	err   error
}

func (r *recordingNamespacePolicyReconciler) EnsureBaseline(_ context.Context, namespace string) error {
	r.calls = append(r.calls, namespace)
	return r.err
}

func TestOperatorSyncHandlerPropagatesNamespaceBaselineError(t *testing.T) {
	template := &v1alpha1.SandboxTemplate{
		ObjectMeta: metav1.ObjectMeta{Name: "template-a", Namespace: "default"},
	}
	indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
	require.NoError(t, indexer.Add(template))

	reconciler := &recordingNamespacePolicyReconciler{err: errors.New("boom")}
	op := &Operator{
		templateLister:  TemplateListerImpl{indexer: indexer},
		namespacePolicy: reconciler,
		logger:          zap.NewNop(),
	}

	err := op.syncHandler(context.Background(), "default/template-a")
	require.Error(t, err)
	assert.Contains(t, err.Error(), "reconcile template namespace baseline")
	assert.Equal(t, []string{"default"}, reconciler.calls)
}

type recordingTemplateStatsPublisher struct {
	calls       int
	idleCount   int32
	activeCount int32
	statsKey    string
}

func (p *recordingTemplateStatsPublisher) PublishTemplateStats(_ context.Context, template *v1alpha1.SandboxTemplate, idleCount, activeCount int32) error {
	p.calls++
	p.idleCount = idleCount
	p.activeCount = activeCount
	p.statsKey = template.Namespace + "/" + template.Name
	return nil
}

func newOperatorTestPod(namespace, name, templateID, poolType string, phase corev1.PodPhase, ready corev1.ConditionStatus) *corev1.Pod {
	return &corev1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Name:      name,
			Namespace: namespace,
			Labels: map[string]string{
				LabelTemplateID: templateID,
				LabelPoolType:   poolType,
			},
		},
		Status: corev1.PodStatus{
			Phase: phase,
			Conditions: []corev1.PodCondition{
				{
					Type:   corev1.PodReady,
					Status: ready,
				},
			},
		},
	}
}
