package distributed_test

import (
	"context"

	"github.com/mudler/LocalAI/core/services/nodes"

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

	pgdriver "gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var _ = Describe("NodeRegistry extra methods", Label("Distributed"), func() {
	var (
		infra    *TestInfra
		db       *gorm.DB
		registry *nodes.NodeRegistry
	)

	BeforeEach(func() {
		infra = SetupInfra("localai_registry_extra_test")

		var err error
		db, err = gorm.Open(pgdriver.Open(infra.PGURL), &gorm.Config{
			Logger: logger.Default.LogMode(logger.Silent),
		})
		Expect(err).ToNot(HaveOccurred())

		registry, err = nodes.NewNodeRegistry(db)
		Expect(err).ToNot(HaveOccurred())
	})

	Context("ListAllLoadedModels", func() {
		It("returns empty when no models loaded", func() {
			models, err := registry.ListAllLoadedModels(context.Background())
			Expect(err).ToNot(HaveOccurred())
			Expect(models).To(BeEmpty())
		})

		It("returns models from healthy nodes with state loaded", func() {
			node := &nodes.BackendNode{
				Name: "healthy-node", Address: "h:5000",
			}
			Expect(registry.Register(context.Background(), node, true)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node.ID, "model-a", 0, "loaded", "", 0)).To(Succeed())

			models, err := registry.ListAllLoadedModels(context.Background())
			Expect(err).ToNot(HaveOccurred())
			Expect(models).To(HaveLen(1))
			Expect(models[0].ModelName).To(Equal("model-a"))
		})

		It("excludes models on unhealthy nodes", func() {
			node := &nodes.BackendNode{
				Name: "sick-node", Address: "s:5000",
			}
			Expect(registry.Register(context.Background(), node, true)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node.ID, "model-on-sick", 0, "loaded", "", 0)).To(Succeed())
			Expect(registry.MarkUnhealthy(context.Background(), node.ID)).To(Succeed())

			models, err := registry.ListAllLoadedModels(context.Background())
			Expect(err).ToNot(HaveOccurred())
			Expect(models).To(BeEmpty())
		})

		It("excludes models with state != loaded", func() {
			node := &nodes.BackendNode{
				Name: "state-node", Address: "st:5000",
			}
			Expect(registry.Register(context.Background(), node, true)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node.ID, "loading-model", 0, "loading", "", 0)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node.ID, "idle-model", 0, "idle", "", 0)).To(Succeed())

			models, err := registry.ListAllLoadedModels(context.Background())
			Expect(err).ToNot(HaveOccurred())
			Expect(models).To(BeEmpty())
		})

		It("returns models across multiple nodes", func() {
			node1 := &nodes.BackendNode{
				Name: "multi-1", Address: "m1:5000",
			}
			node2 := &nodes.BackendNode{
				Name: "multi-2", Address: "m2:5000",
			}
			Expect(registry.Register(context.Background(), node1, true)).To(Succeed())
			Expect(registry.Register(context.Background(), node2, true)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node1.ID, "model-x", 0, "loaded", "", 0)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node2.ID, "model-y", 0, "loaded", "", 0)).To(Succeed())

			models, err := registry.ListAllLoadedModels(context.Background())
			Expect(err).ToNot(HaveOccurred())
			Expect(models).To(HaveLen(2))

			names := map[string]bool{}
			for _, m := range models {
				names[m.ModelName] = true
			}
			Expect(names).To(HaveKey("model-x"))
			Expect(names).To(HaveKey("model-y"))
		})
	})

	Context("FindNodeForModel", func() {
		It("returns (node, true) when model is loaded on healthy node", func() {
			node := &nodes.BackendNode{
				Name: "find-node", Address: "f:5000",
			}
			Expect(registry.Register(context.Background(), node, true)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node.ID, "findable-model", 0, "loaded", "", 0)).To(Succeed())

			found, ok := registry.FindNodeForModel(context.Background(), "findable-model")
			Expect(ok).To(BeTrue())
			Expect(found).ToNot(BeNil())
			Expect(found.ID).To(Equal(node.ID))
		})

		It("returns (nil, false) when model not found", func() {
			found, ok := registry.FindNodeForModel(context.Background(), "no-such-model")
			Expect(ok).To(BeFalse())
			Expect(found).To(BeNil())
		})

		It("returns (nil, false) when model only on unhealthy node", func() {
			node := &nodes.BackendNode{
				Name: "unhealthy-find", Address: "uf:5000",
			}
			Expect(registry.Register(context.Background(), node, true)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node.ID, "unhealthy-model", 0, "loaded", "", 0)).To(Succeed())
			Expect(registry.MarkUnhealthy(context.Background(), node.ID)).To(Succeed())

			found, ok := registry.FindNodeForModel(context.Background(), "unhealthy-model")
			Expect(ok).To(BeFalse())
			Expect(found).To(BeNil())
		})
	})

	Context("Register clears stale models", func() {
		It("clears node_models when a node re-registers", func() {
			node := &nodes.BackendNode{
				Name: "stale-clear-node", Address: "sc:5000",
			}
			Expect(registry.Register(context.Background(), node, true)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node.ID, "stale-model-1", 0, "loaded", "", 0)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node.ID, "stale-model-2", 0, "loaded", "", 0)).To(Succeed())

			// Verify models exist
			models, err := registry.GetNodeModels(context.Background(), node.ID)
			Expect(err).ToNot(HaveOccurred())
			Expect(models).To(HaveLen(2))

			// Re-register the same node (simulates restart)
			reNode := &nodes.BackendNode{
				Name: "stale-clear-node", Address: "sc:5001",
			}
			Expect(registry.Register(context.Background(), reNode, true)).To(Succeed())

			// Stale models should be cleared
			models, err = registry.GetNodeModels(context.Background(), node.ID)
			Expect(err).ToNot(HaveOccurred())
			Expect(models).To(BeEmpty())
		})
	})

	Context("FindIdleNode", func() {
		It("returns a healthy node with no loaded models", func() {
			node := &nodes.BackendNode{
				Name: "idle-node", Address: "idle:5000",
			}
			Expect(registry.Register(context.Background(), node, true)).To(Succeed())

			found, err := registry.FindIdleNode(context.Background())
			Expect(err).ToNot(HaveOccurred())
			Expect(found).ToNot(BeNil())
			Expect(found.ID).To(Equal(node.ID))
		})

		It("skips nodes that have loaded models", func() {
			busy := &nodes.BackendNode{
				Name: "busy-node", Address: "busy:5000",
			}
			idle := &nodes.BackendNode{
				Name: "idle-node-2", Address: "idle2:5000",
			}
			Expect(registry.Register(context.Background(), busy, true)).To(Succeed())
			Expect(registry.Register(context.Background(), idle, true)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), busy.ID, "some-model", 0, "loaded", "", 0)).To(Succeed())

			found, err := registry.FindIdleNode(context.Background())
			Expect(err).ToNot(HaveOccurred())
			Expect(found).ToNot(BeNil())
			Expect(found.ID).To(Equal(idle.ID))
		})

		It("returns error when no idle nodes exist", func() {
			node := &nodes.BackendNode{
				Name: "loaded-node", Address: "loaded:5000",
			}
			Expect(registry.Register(context.Background(), node, true)).To(Succeed())
			Expect(registry.SetNodeModel(context.Background(), node.ID, "model-x", 0, "loaded", "", 0)).To(Succeed())

			_, err := registry.FindIdleNode(context.Background())
			Expect(err).To(HaveOccurred())
		})
	})
})
