package config

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
)

// setTestHome sets both HOME (Unix) and USERPROFILE (Windows) for cross-platform tests
func setTestHome(t *testing.T, dir string) {
	t.Setenv("HOME", dir)
	t.Setenv("USERPROFILE", dir)
}

// editorPaths is a test helper that safely calls Paths if the runner implements Editor
func editorPaths(r Runner) []string {
	if editor, ok := r.(Editor); ok {
		return editor.Paths()
	}
	return nil
}

func TestIntegrationConfig(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	t.Run("save and load round-trip", func(t *testing.T) {
		models := []string{"llama3.2", "mistral", "qwen2.5"}
		if err := saveIntegration("claude", models); err != nil {
			t.Fatal(err)
		}

		config, err := loadIntegration("claude")
		if err != nil {
			t.Fatal(err)
		}

		if len(config.Models) != len(models) {
			t.Errorf("expected %d models, got %d", len(models), len(config.Models))
		}
		for i, m := range models {
			if config.Models[i] != m {
				t.Errorf("model %d: expected %s, got %s", i, m, config.Models[i])
			}
		}
	})

	t.Run("defaultModel returns first model", func(t *testing.T) {
		saveIntegration("codex", []string{"model-a", "model-b"})

		config, _ := loadIntegration("codex")
		defaultModel := ""
		if len(config.Models) > 0 {
			defaultModel = config.Models[0]
		}
		if defaultModel != "model-a" {
			t.Errorf("expected model-a, got %s", defaultModel)
		}
	})

	t.Run("defaultModel returns empty for no models", func(t *testing.T) {
		config := &integration{Models: []string{}}
		defaultModel := ""
		if len(config.Models) > 0 {
			defaultModel = config.Models[0]
		}
		if defaultModel != "" {
			t.Errorf("expected empty string, got %s", defaultModel)
		}
	})

	t.Run("app name is case-insensitive", func(t *testing.T) {
		saveIntegration("Claude", []string{"model-x"})

		config, err := loadIntegration("claude")
		if err != nil {
			t.Fatal(err)
		}
		defaultModel := ""
		if len(config.Models) > 0 {
			defaultModel = config.Models[0]
		}
		if defaultModel != "model-x" {
			t.Errorf("expected model-x, got %s", defaultModel)
		}
	})

	t.Run("multiple integrations in single file", func(t *testing.T) {
		saveIntegration("app1", []string{"model-1"})
		saveIntegration("app2", []string{"model-2"})

		config1, _ := loadIntegration("app1")
		config2, _ := loadIntegration("app2")

		defaultModel1 := ""
		if len(config1.Models) > 0 {
			defaultModel1 = config1.Models[0]
		}
		defaultModel2 := ""
		if len(config2.Models) > 0 {
			defaultModel2 = config2.Models[0]
		}
		if defaultModel1 != "model-1" {
			t.Errorf("expected model-1, got %s", defaultModel1)
		}
		if defaultModel2 != "model-2" {
			t.Errorf("expected model-2, got %s", defaultModel2)
		}
	})
}

func TestListIntegrations(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	t.Run("returns empty when no integrations", func(t *testing.T) {
		configs, err := listIntegrations()
		if err != nil {
			t.Fatal(err)
		}
		if len(configs) != 0 {
			t.Errorf("expected 0 integrations, got %d", len(configs))
		}
	})

	t.Run("returns all saved integrations", func(t *testing.T) {
		saveIntegration("claude", []string{"model-1"})
		saveIntegration("droid", []string{"model-2"})

		configs, err := listIntegrations()
		if err != nil {
			t.Fatal(err)
		}
		if len(configs) != 2 {
			t.Errorf("expected 2 integrations, got %d", len(configs))
		}
	})
}

func TestEditorPaths(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	t.Run("returns empty for claude (no Editor)", func(t *testing.T) {
		r := integrations["claude"]
		paths := editorPaths(r)
		if len(paths) != 0 {
			t.Errorf("expected no paths for claude, got %v", paths)
		}
	})

	t.Run("returns empty for codex (no Editor)", func(t *testing.T) {
		r := integrations["codex"]
		paths := editorPaths(r)
		if len(paths) != 0 {
			t.Errorf("expected no paths for codex, got %v", paths)
		}
	})

	t.Run("returns empty for droid when no config exists", func(t *testing.T) {
		r := integrations["droid"]
		paths := editorPaths(r)
		if len(paths) != 0 {
			t.Errorf("expected no paths, got %v", paths)
		}
	})

	t.Run("returns path for droid when config exists", func(t *testing.T) {
		settingsDir, _ := os.UserHomeDir()
		settingsDir = filepath.Join(settingsDir, ".factory")
		os.MkdirAll(settingsDir, 0o755)
		os.WriteFile(filepath.Join(settingsDir, "settings.json"), []byte(`{}`), 0o644)

		r := integrations["droid"]
		paths := editorPaths(r)
		if len(paths) != 1 {
			t.Errorf("expected 1 path, got %d", len(paths))
		}
	})

	t.Run("returns paths for opencode when configs exist", func(t *testing.T) {
		home, _ := os.UserHomeDir()
		configDir := filepath.Join(home, ".config", "opencode")
		stateDir := filepath.Join(home, ".local", "state", "opencode")
		os.MkdirAll(configDir, 0o755)
		os.MkdirAll(stateDir, 0o755)
		os.WriteFile(filepath.Join(configDir, "opencode.json"), []byte(`{}`), 0o644)
		os.WriteFile(filepath.Join(stateDir, "model.json"), []byte(`{}`), 0o644)

		r := integrations["opencode"]
		paths := editorPaths(r)
		if len(paths) != 2 {
			t.Errorf("expected 2 paths, got %d: %v", len(paths), paths)
		}
	})
}

func TestLoadIntegration_CorruptedJSON(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	// Create corrupted config.json file
	dir := filepath.Join(tmpDir, ".ollama", "config")
	os.MkdirAll(dir, 0o755)
	os.WriteFile(filepath.Join(dir, "config.json"), []byte(`{corrupted json`), 0o644)

	// Corrupted file is treated as empty, so loadIntegration returns not found
	_, err := loadIntegration("test")
	if err == nil {
		t.Error("expected error for nonexistent integration in corrupted file")
	}
}

func TestSaveIntegration_NilModels(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	if err := saveIntegration("test", nil); err != nil {
		t.Fatalf("saveIntegration with nil models failed: %v", err)
	}

	config, err := loadIntegration("test")
	if err != nil {
		t.Fatalf("loadIntegration failed: %v", err)
	}

	if config.Models == nil {
		// nil is acceptable
	} else if len(config.Models) != 0 {
		t.Errorf("expected empty or nil models, got %v", config.Models)
	}
}

func TestSaveIntegration_EmptyAppName(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	err := saveIntegration("", []string{"model"})
	if err == nil {
		t.Error("expected error for empty app name, got nil")
	}
	if err != nil && !strings.Contains(err.Error(), "app name cannot be empty") {
		t.Errorf("expected 'app name cannot be empty' error, got: %v", err)
	}
}

func TestLoadIntegration_NonexistentIntegration(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	_, err := loadIntegration("nonexistent")
	if err == nil {
		t.Error("expected error for nonexistent integration, got nil")
	}
	if !os.IsNotExist(err) {
		t.Logf("error type is os.ErrNotExist as expected: %v", err)
	}
}

func TestConfigPath(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	path, err := configPath()
	if err != nil {
		t.Fatal(err)
	}

	expected := filepath.Join(tmpDir, ".ollama", "config", "config.json")
	if path != expected {
		t.Errorf("expected %s, got %s", expected, path)
	}
}

func TestLoad(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	t.Run("returns empty config when file does not exist", func(t *testing.T) {
		cfg, err := load()
		if err != nil {
			t.Fatal(err)
		}
		if cfg == nil {
			t.Fatal("expected non-nil config")
		}
		if cfg.Integrations == nil {
			t.Error("expected non-nil Integrations map")
		}
		if len(cfg.Integrations) != 0 {
			t.Errorf("expected empty Integrations, got %d", len(cfg.Integrations))
		}
	})

	t.Run("loads existing config", func(t *testing.T) {
		path, _ := configPath()
		os.MkdirAll(filepath.Dir(path), 0o755)
		os.WriteFile(path, []byte(`{"integrations":{"test":{"models":["model-a"]}}}`), 0o644)

		cfg, err := load()
		if err != nil {
			t.Fatal(err)
		}
		if cfg.Integrations["test"] == nil {
			t.Fatal("expected test integration")
		}
		if len(cfg.Integrations["test"].Models) != 1 {
			t.Errorf("expected 1 model, got %d", len(cfg.Integrations["test"].Models))
		}
	})

	t.Run("returns error for corrupted JSON", func(t *testing.T) {
		path, _ := configPath()
		os.MkdirAll(filepath.Dir(path), 0o755)
		os.WriteFile(path, []byte(`{corrupted`), 0o644)

		_, err := load()
		if err == nil {
			t.Error("expected error for corrupted JSON")
		}
	})
}

func TestSave(t *testing.T) {
	tmpDir := t.TempDir()
	setTestHome(t, tmpDir)

	t.Run("creates config file", func(t *testing.T) {
		cfg := &config{
			Integrations: map[string]*integration{
				"test": {Models: []string{"model-a", "model-b"}},
			},
		}

		if err := save(cfg); err != nil {
			t.Fatal(err)
		}

		path, _ := configPath()
		if _, err := os.Stat(path); os.IsNotExist(err) {
			t.Error("config file was not created")
		}
	})

	t.Run("round-trip preserves data", func(t *testing.T) {
		cfg := &config{
			Integrations: map[string]*integration{
				"claude": {Models: []string{"llama3.2", "mistral"}},
				"codex":  {Models: []string{"qwen2.5"}},
			},
		}

		if err := save(cfg); err != nil {
			t.Fatal(err)
		}

		loaded, err := load()
		if err != nil {
			t.Fatal(err)
		}

		if len(loaded.Integrations) != 2 {
			t.Errorf("expected 2 integrations, got %d", len(loaded.Integrations))
		}
		if loaded.Integrations["claude"] == nil {
			t.Error("missing claude integration")
		}
		if len(loaded.Integrations["claude"].Models) != 2 {
			t.Errorf("expected 2 models for claude, got %d", len(loaded.Integrations["claude"].Models))
		}
	})
}
