package route

import (
	"context"
	"testing"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/client-go/kubernetes/fake"
)

func TestK8sDiscovery_FindService(t *testing.T) {
	tests := []struct {
		name      string
		services  []*corev1.Service
		namespace string
		pattern   string
		want      string
		wantErr   bool
		verify    func(t *testing.T, discovery ServiceDiscovery, client *fake.Clientset, firstResult string)
	}{
		{
			name: "match and cache persists after delete",
			services: []*corev1.Service{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "db-123-pd",
						Namespace: "tidb123",
					},
				},
				{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "db-123-tikv",
						Namespace: "tidb123",
					},
				},
			},
			namespace: "tidb123",
			pattern:   "^db-[0-9]+-pd$",
			want:      "db-123-pd",
			verify: func(t *testing.T, discovery ServiceDiscovery, client *fake.Clientset, firstResult string) {
				if err := client.CoreV1().Services("tidb123").Delete(context.Background(), "db-123-pd", metav1.DeleteOptions{}); err != nil {
					t.Fatalf("cleanup delete failed: %v", err)
				}

				cached, err := discovery.FindService(context.Background(), "tidb123", "^db-[0-9]+-pd$")
				if err != nil {
					t.Fatalf("unexpected cached error: %v", err)
				}
				if cached != firstResult {
					t.Fatalf("cached result changed: got %s", cached)
				}
			},
		},
		{
			name: "no match returns error and is not cached",
			services: []*corev1.Service{
				{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "db-123-pd",
						Namespace: "tidb123",
					},
				},
			},
			namespace: "tidb123",
			pattern:   "^db-[0-9]+-tikv$",
			wantErr:   true,
		},
		{
			name:      "not found is not cached - service created later is discovered",
			services:  []*corev1.Service{}, // Start with no services
			namespace: "tidb123",
			pattern:   "^db-[0-9]+-pd$",
			wantErr:   true,
			verify: func(t *testing.T, discovery ServiceDiscovery, client *fake.Clientset, _ string) {
				// Create the service after first lookup
				_, err := client.CoreV1().Services("tidb123").Create(
					context.Background(),
					&corev1.Service{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "db-123-pd",
							Namespace: "tidb123",
						},
					},
					metav1.CreateOptions{},
				)
				if err != nil {
					t.Fatalf("failed to create service: %v", err)
				}

				// Second lookup should find the newly created service
				got, err := discovery.FindService(context.Background(), "tidb123", "^db-[0-9]+-pd$")
				if err != nil {
					t.Fatalf("expected to find service after creation, got error: %v", err)
				}
				if got != "db-123-pd" {
					t.Fatalf("expected db-123-pd, got %s", got)
				}
			},
		},
		{
			name:      "invalid pattern returns error consistently",
			namespace: "default",
			pattern:   "[",
			wantErr:   true,
			verify: func(t *testing.T, discovery ServiceDiscovery, client *fake.Clientset, _ string) {
				if _, err := discovery.FindService(context.Background(), "default", "["); err == nil {
					t.Fatal("expected pattern error on second call, got nil")
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			objects := make([]runtime.Object, 0, len(tt.services))
			for _, svc := range tt.services {
				objects = append(objects, svc.DeepCopy())
			}

			client := fake.NewClientset(objects...)
			discovery := NewServiceDiscoveryWithClient(client)

			got, err := discovery.FindService(context.Background(), tt.namespace, tt.pattern)
			if tt.wantErr {
				if err == nil {
					t.Fatal("expected error, got nil")
				}
			} else {
				if err != nil {
					t.Fatalf("unexpected error: %v", err)
				}
				if got != tt.want {
					t.Fatalf("got %s, want %s", got, tt.want)
				}
			}

			if tt.verify != nil {
				tt.verify(t, discovery, client, got)
			}
		})
	}
}
