package gallery_test

import (
	"context"
	"errors"
	"os"
	"path/filepath"

	"github.com/mudler/LocalAI/core/config"
	. "github.com/mudler/LocalAI/core/gallery"
	"github.com/mudler/LocalAI/pkg/system"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"gopkg.in/yaml.v3"
)

const bertEmbeddingsURL = `https://gist.githubusercontent.com/mudler/0a080b166b87640e8644b09c2aee6e3b/raw/f0e8c26bb72edc16d9fbafbfd6638072126ff225/bert-embeddings-gallery.yaml`

var _ = Describe("Model test", func() {

	BeforeEach(func() {
		if os.Getenv("FIXTURES") == "" {
			Skip("FIXTURES env var not set, skipping model tests")
		}
	})

	Context("Downloading", func() {
		It("applies model correctly", func() {
			tempdir, err := os.MkdirTemp("", "test")
			Expect(err).ToNot(HaveOccurred())
			defer os.RemoveAll(tempdir)
			c, err := ReadConfigFile[ModelConfig](filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml"))
			Expect(err).ToNot(HaveOccurred())
			systemState, err := system.GetSystemState(
				system.WithModelPath(tempdir),
			)
			Expect(err).ToNot(HaveOccurred())
			_, err = InstallModel(context.TODO(), systemState, "", c, map[string]interface{}{}, func(string, string, string, float64) {}, true)
			Expect(err).ToNot(HaveOccurred())

			for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "cerebras.yaml"} {
				_, err = os.Stat(filepath.Join(tempdir, f))
				Expect(err).ToNot(HaveOccurred())
			}

			content := map[string]interface{}{}

			dat, err := os.ReadFile(filepath.Join(tempdir, "cerebras.yaml"))
			Expect(err).ToNot(HaveOccurred())

			err = yaml.Unmarshal(dat, content)
			Expect(err).ToNot(HaveOccurred())

			Expect(content["context_size"]).To(Equal(1024))
		})

		It("applies model from gallery correctly", func() {
			tempdir, err := os.MkdirTemp("", "test")
			Expect(err).ToNot(HaveOccurred())
			defer os.RemoveAll(tempdir)

			gallery := []GalleryModel{{
				Metadata: Metadata{
					Name: "bert",
					URL:  bertEmbeddingsURL,
				},
			}}
			out, err := yaml.Marshal(gallery)
			Expect(err).ToNot(HaveOccurred())
			galleryFilePath := filepath.Join(tempdir, "gallery_simple.yaml")
			err = os.WriteFile(galleryFilePath, out, 0600)
			Expect(err).ToNot(HaveOccurred())
			Expect(filepath.IsAbs(galleryFilePath)).To(BeTrue(), galleryFilePath)
			galleries := []config.Gallery{
				{
					Name: "test",
					URL:  "file://" + galleryFilePath,
				},
			}
			systemState, err := system.GetSystemState(
				system.WithModelPath(tempdir),
			)
			Expect(err).ToNot(HaveOccurred())

			models, err := AvailableGalleryModels(galleries, systemState)
			Expect(err).ToNot(HaveOccurred())
			Expect(len(models)).To(Equal(1))
			Expect(models[0].Name).To(Equal("bert"))
			Expect(models[0].URL).To(Equal(bertEmbeddingsURL))
			Expect(models[0].Installed).To(BeFalse())

			err = InstallModelFromGallery(context.TODO(), galleries, []config.Gallery{}, systemState, nil, "test@bert", GalleryModel{}, func(s1, s2, s3 string, f float64) {}, true, true)
			Expect(err).ToNot(HaveOccurred())

			dat, err := os.ReadFile(filepath.Join(tempdir, "bert.yaml"))
			Expect(err).ToNot(HaveOccurred())

			content := map[string]interface{}{}
			err = yaml.Unmarshal(dat, &content)
			Expect(err).ToNot(HaveOccurred())
			Expect(content["usage"]).To(ContainSubstring("You can test this model with curl like this"))

			models, err = AvailableGalleryModels(galleries, systemState)
			Expect(err).ToNot(HaveOccurred())
			Expect(len(models)).To(Equal(1))
			Expect(models[0].Installed).To(BeTrue())

			// delete
			err = DeleteModelFromSystem(systemState, "bert")
			Expect(err).ToNot(HaveOccurred())

			models, err = AvailableGalleryModels(galleries, systemState)
			Expect(err).ToNot(HaveOccurred())
			Expect(len(models)).To(Equal(1))
			Expect(models[0].Installed).To(BeFalse())

			_, err = os.Stat(filepath.Join(tempdir, "bert.yaml"))
			Expect(err).To(HaveOccurred())
			Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue())
		})

		It("renames model correctly", func() {
			tempdir, err := os.MkdirTemp("", "test")
			Expect(err).ToNot(HaveOccurred())
			defer os.RemoveAll(tempdir)
			c, err := ReadConfigFile[ModelConfig](filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml"))
			Expect(err).ToNot(HaveOccurred())

			systemState, err := system.GetSystemState(
				system.WithModelPath(tempdir),
			)
			Expect(err).ToNot(HaveOccurred())
			_, err = InstallModel(context.TODO(), systemState, "foo", c, map[string]interface{}{}, func(string, string, string, float64) {}, true)
			Expect(err).ToNot(HaveOccurred())

			for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "foo.yaml"} {
				_, err = os.Stat(filepath.Join(tempdir, f))
				Expect(err).ToNot(HaveOccurred())
			}
		})

		It("overrides parameters", func() {
			tempdir, err := os.MkdirTemp("", "test")
			Expect(err).ToNot(HaveOccurred())
			defer os.RemoveAll(tempdir)
			c, err := ReadConfigFile[ModelConfig](filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml"))
			Expect(err).ToNot(HaveOccurred())

			systemState, err := system.GetSystemState(
				system.WithModelPath(tempdir),
			)
			Expect(err).ToNot(HaveOccurred())
			_, err = InstallModel(context.TODO(), systemState, "foo", c, map[string]interface{}{"backend": "foo"}, func(string, string, string, float64) {}, true)
			Expect(err).ToNot(HaveOccurred())

			for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "foo.yaml"} {
				_, err = os.Stat(filepath.Join(tempdir, f))
				Expect(err).ToNot(HaveOccurred())
			}

			content := map[string]interface{}{}

			dat, err := os.ReadFile(filepath.Join(tempdir, "foo.yaml"))
			Expect(err).ToNot(HaveOccurred())

			err = yaml.Unmarshal(dat, content)
			Expect(err).ToNot(HaveOccurred())

			Expect(content["backend"]).To(Equal("foo"))
		})

		It("catches path traversals", func() {
			tempdir, err := os.MkdirTemp("", "test")
			Expect(err).ToNot(HaveOccurred())
			defer os.RemoveAll(tempdir)
			c, err := ReadConfigFile[ModelConfig](filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml"))
			Expect(err).ToNot(HaveOccurred())

			systemState, err := system.GetSystemState(
				system.WithModelPath(tempdir),
			)
			Expect(err).ToNot(HaveOccurred())
			_, err = InstallModel(context.TODO(), systemState, "../../../foo", c, map[string]interface{}{}, func(string, string, string, float64) {}, true)
			Expect(err).To(HaveOccurred())
		})

		It("handles nil configOverrides without panic", func() {
			tempdir, err := os.MkdirTemp("", "test")
			Expect(err).ToNot(HaveOccurred())
			defer os.RemoveAll(tempdir)
			c, err := ReadConfigFile[ModelConfig](filepath.Join(os.Getenv("FIXTURES"), "gallery_simple.yaml"))
			Expect(err).ToNot(HaveOccurred())

			systemState, err := system.GetSystemState(
				system.WithModelPath(tempdir),
			)
			Expect(err).ToNot(HaveOccurred())
			_, err = InstallModel(context.TODO(), systemState, "test-model", c, nil, func(string, string, string, float64) {}, true)
			Expect(err).ToNot(HaveOccurred())

			for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "test-model.yaml"} {
				_, err = os.Stat(filepath.Join(tempdir, f))
				Expect(err).ToNot(HaveOccurred())
			}
		})

		It("does not delete shared model files when one config is deleted", func() {
			tempdir, err := os.MkdirTemp("", "test")
			Expect(err).ToNot(HaveOccurred())
			defer os.RemoveAll(tempdir)

			systemState, err := system.GetSystemState(
				system.WithModelPath(tempdir),
			)
			Expect(err).ToNot(HaveOccurred())

			// Create a shared model file
			sharedModelFile := filepath.Join(tempdir, "shared_model.bin")
			err = os.WriteFile(sharedModelFile, []byte("fake model content"), 0600)
			Expect(err).ToNot(HaveOccurred())

			// Create first model configuration
			config1 := `name: model1
model: shared_model.bin`
			err = os.WriteFile(filepath.Join(tempdir, "model1.yaml"), []byte(config1), 0600)
			Expect(err).ToNot(HaveOccurred())

			// Create first model's gallery file
			galleryConfig1 := ModelConfig{
				Name: "model1",
				Files: []File{
					{Filename: "shared_model.bin"},
				},
			}
			galleryData1, err := yaml.Marshal(galleryConfig1)
			Expect(err).ToNot(HaveOccurred())
			err = os.WriteFile(filepath.Join(tempdir, "._gallery_model1.yaml"), galleryData1, 0600)
			Expect(err).ToNot(HaveOccurred())

			// Create second model configuration sharing the same model file
			config2 := `name: model2
model: shared_model.bin`
			err = os.WriteFile(filepath.Join(tempdir, "model2.yaml"), []byte(config2), 0600)
			Expect(err).ToNot(HaveOccurred())

			// Create second model's gallery file
			galleryConfig2 := ModelConfig{
				Name: "model2",
				Files: []File{
					{Filename: "shared_model.bin"},
				},
			}
			galleryData2, err := yaml.Marshal(galleryConfig2)
			Expect(err).ToNot(HaveOccurred())
			err = os.WriteFile(filepath.Join(tempdir, "._gallery_model2.yaml"), galleryData2, 0600)
			Expect(err).ToNot(HaveOccurred())

			// Verify both configurations exist
			_, err = os.Stat(filepath.Join(tempdir, "model1.yaml"))
			Expect(err).ToNot(HaveOccurred())
			_, err = os.Stat(filepath.Join(tempdir, "model2.yaml"))
			Expect(err).ToNot(HaveOccurred())

			// Verify the shared model file exists
			_, err = os.Stat(sharedModelFile)
			Expect(err).ToNot(HaveOccurred())

			// Delete the first model
			err = DeleteModelFromSystem(systemState, "model1")
			Expect(err).ToNot(HaveOccurred())

			// Verify the first configuration is deleted
			_, err = os.Stat(filepath.Join(tempdir, "model1.yaml"))
			Expect(err).To(HaveOccurred())
			Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue())

			// Verify the shared model file still exists (not deleted because model2 still uses it)
			_, err = os.Stat(sharedModelFile)
			Expect(err).ToNot(HaveOccurred(), "shared model file should not be deleted when used by other configs")

			// Verify the second configuration still exists
			_, err = os.Stat(filepath.Join(tempdir, "model2.yaml"))
			Expect(err).ToNot(HaveOccurred())

			// Now delete the second model
			err = DeleteModelFromSystem(systemState, "model2")
			Expect(err).ToNot(HaveOccurred())

			// Verify the second configuration is deleted
			_, err = os.Stat(filepath.Join(tempdir, "model2.yaml"))
			Expect(err).To(HaveOccurred())
			Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue())

			// Verify the shared model file is now deleted (no more references)
			_, err = os.Stat(sharedModelFile)
			Expect(err).To(HaveOccurred(), "shared model file should be deleted when no configs reference it")
			Expect(errors.Is(err, os.ErrNotExist)).To(BeTrue())
		})
	})
})
