package config

import (
	"encoding/json"
	"os"
	"path/filepath"
	"reflect"
	"testing"
)

func TestConfigPathPrefersXDGConfigHome(t *testing.T) {
	xdgHome := t.TempDir()
	t.Setenv("XDG_CONFIG_HOME", xdgHome)
	t.Setenv("HOME", filepath.Join(t.TempDir(), "home"))

	path, err := ConfigPath()
	if err != nil {
		t.Fatalf("ConfigPath() error = %v", err)
	}

	want := filepath.Join(xdgHome, "clinic", "config.json")
	if path != want {
		t.Fatalf("ConfigPath() = %q, want %q", path, want)
	}
}

func TestConfigPathFallsBackToHomeConfigDir(t *testing.T) {
	home := filepath.Join(t.TempDir(), "home")
	if err := os.MkdirAll(home, 0o755); err != nil {
		t.Fatalf("MkdirAll(home) error = %v", err)
	}

	t.Setenv("XDG_CONFIG_HOME", "")
	t.Setenv("HOME", home)

	path, err := ConfigPath()
	if err != nil {
		t.Fatalf("ConfigPath() error = %v", err)
	}

	want := filepath.Join(home, ".config", "clinic", "config.json")
	if path != want {
		t.Fatalf("ConfigPath() = %q, want %q", path, want)
	}
}

func TestLoadOrInitReturnsFirstRunDefaults(t *testing.T) {
	xdgHome := t.TempDir()
	t.Setenv("XDG_CONFIG_HOME", xdgHome)
	t.Setenv("HOME", filepath.Join(t.TempDir(), "home"))

	cfg, err := LoadOrInit()
	if err != nil {
		t.Fatalf("LoadOrInit() error = %v", err)
	}

	if cfg.ActiveEnv != "prod" {
		t.Fatalf("ActiveEnv = %q, want %q", cfg.ActiveEnv, "prod")
	}

	for name, wantURL := range map[string]string{
		"prod":    "https://clinic.pingcap.com",
		"staging": "https://staging-clinic.pingcap.com",
		"dev":     "https://dev-clinic.pingcap.com",
	} {
		env, ok := cfg.Environments[name]
		if !ok {
			t.Fatalf("Environments[%q] missing", name)
		}
		if env.URL != wantURL {
			t.Fatalf("Environments[%q].URL = %q, want %q", name, env.URL, wantURL)
		}
	}

	for _, name := range []string{"prod", "staging", "dev"} {
		container, ok := cfg.EnvProfiles[name]
		if !ok {
			t.Fatalf("EnvProfiles[%q] missing", name)
		}
		if container.ActiveProfile != "" {
			t.Fatalf("EnvProfiles[%q].ActiveProfile = %q, want empty", name, container.ActiveProfile)
		}
		if container.Profiles == nil {
			t.Fatalf("EnvProfiles[%q].Profiles = nil, want empty map", name)
		}
		if len(container.Profiles) != 0 {
			t.Fatalf("EnvProfiles[%q].Profiles len = %d, want 0", name, len(container.Profiles))
		}
	}

	raw := mustReadJSONFile(t)
	assertTopLevelConfigShape(t, raw)

	envProfiles := mustMap(t, raw["env_profiles"])
	for _, name := range []string{"prod", "staging", "dev"} {
		container := mustMap(t, envProfiles[name])
		if _, ok := container["active_profile"]; ok {
			t.Fatalf("env_profiles[%q] unexpectedly contains active_profile", name)
		}
		profiles := mustMap(t, container["profiles"])
		if len(profiles) != 0 {
			t.Fatalf("env_profiles[%q].profiles len = %d, want 0", name, len(profiles))
		}
	}

	resolved, err := cfg.ResolveEnv("")
	if err != nil {
		t.Fatalf("ResolveEnv(\"\") error = %v", err)
	}
	if resolved.URL != "https://clinic.pingcap.com" {
		t.Fatalf("ResolveEnv(\"\").URL = %q, want %q", resolved.URL, "https://clinic.pingcap.com")
	}

	resolved, err = cfg.ResolveEnv("staging")
	if err != nil {
		t.Fatalf("ResolveEnv(\"staging\") error = %v", err)
	}
	if resolved.URL != "https://staging-clinic.pingcap.com" {
		t.Fatalf("ResolveEnv(\"staging\").URL = %q, want %q", resolved.URL, "https://staging-clinic.pingcap.com")
	}
}

func TestLoadOrInitDropsLegacyProfileAndClusterCacheData(t *testing.T) {
	xdgHome := t.TempDir()
	t.Setenv("XDG_CONFIG_HOME", xdgHome)
	t.Setenv("HOME", filepath.Join(t.TempDir(), "home"))

	path, err := ConfigPath()
	if err != nil {
		t.Fatalf("ConfigPath() error = %v", err)
	}

	legacy := `{
  "active_env": "staging",
  "environments": {
    "prod": {
      "url": "https://clinic.pingcap.com",
      "api_key": "prod-key"
    },
    "staging": {
      "url": "https://staging-clinic.pingcap.com",
      "api_key": "staging-key"
    },
    "dev": {
      "url": "https://dev-clinic.pingcap.com"
    }
  },
  "profiles": {
    "staging": {
      "env": "staging",
      "cluster_id": "legacy-cluster",
      "range_start": "10"
    }
  },
  "cluster_cache": {
    "staging": [
      {
        "cluster_id": "cluster-1",
        "alias": "primary",
        "org_id": "org-1",
        "parent_id": "pool-1",
        "cluster_deploy_type_v2": "premium"
      }
    ]
  }
}`
	if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
		t.Fatalf("MkdirAll(config dir) error = %v", err)
	}
	if err := os.WriteFile(path, []byte(legacy), 0o600); err != nil {
		t.Fatalf("WriteFile(legacy config) error = %v", err)
	}

	cfg, err := LoadOrInit()
	if err != nil {
		t.Fatalf("LoadOrInit() error = %v", err)
	}

	if cfg.ActiveEnv != "staging" {
		t.Fatalf("ActiveEnv = %q, want %q", cfg.ActiveEnv, "staging")
	}

	container, ok := cfg.EnvProfiles["staging"]
	if !ok {
		t.Fatalf("EnvProfiles[%q] missing", "staging")
	}
	if container.ActiveProfile != "" {
		t.Fatalf("EnvProfiles[%q].ActiveProfile = %q, want empty", "staging", container.ActiveProfile)
	}
	if len(container.Profiles) != 0 {
		t.Fatalf("EnvProfiles[%q].Profiles len = %d, want 0", "staging", len(container.Profiles))
	}

	raw := mustReadJSONFile(t)
	assertTopLevelConfigShape(t, raw)
	if _, ok := raw["profiles"]; ok {
		t.Fatalf("legacy top-level profiles key was preserved")
	}
	if _, ok := raw["cluster_cache"]; ok {
		t.Fatalf("legacy top-level cluster_cache key was preserved")
	}

	envProfiles := mustMap(t, raw["env_profiles"])
	staging := mustMap(t, envProfiles["staging"])
	if _, ok := staging["active_profile"]; ok {
		t.Fatalf("env_profiles[\"staging\"] unexpectedly contains active_profile")
	}
	if profiles := mustMap(t, staging["profiles"]); len(profiles) != 0 {
		t.Fatalf("env_profiles[\"staging\"].profiles len = %d, want 0", len(profiles))
	}
}

func TestSaveAndLoadRoundTripPreservesConfigAndPermissions(t *testing.T) {
	xdgHome := t.TempDir()
	t.Setenv("XDG_CONFIG_HOME", xdgHome)
	t.Setenv("HOME", filepath.Join(t.TempDir(), "home"))

	cfg := &Config{
		ActiveEnv: "staging",
		Environments: map[string]Environment{
			"prod": {
				URL:    "https://clinic.pingcap.com",
				APIKey: "prod-key",
			},
			"staging": {
				URL:    "https://staging-clinic.pingcap.com/custom",
				APIKey: "staging-key",
			},
			"dev": {
				URL: "https://dev-clinic.pingcap.com",
			},
		},
		EnvProfiles: map[string]EnvProfileContainer{
			"prod": {
				Profiles: map[string]ProfileRecord{},
			},
			"staging": {
				ActiveProfile: "blue",
				Profiles: map[string]ProfileRecord{
					"blue": {
						ClusterID:           "cluster-1",
						ClusterName:         "primary",
						OrgID:               "org-1",
						ParentID:            "pool-1",
						ClusterDeployTypeV2: "premium",
						RangeStart:          "100",
						RangeEnd:            "200",
					},
				},
			},
			"dev": {
				Profiles: map[string]ProfileRecord{},
			},
		},
	}

	if err := cfg.Save(); err != nil {
		t.Fatalf("Save() error = %v", err)
	}

	path, err := ConfigPath()
	if err != nil {
		t.Fatalf("ConfigPath() error = %v", err)
	}

	info, err := os.Stat(path)
	if err != nil {
		t.Fatalf("Stat(config file) error = %v", err)
	}
	if got := info.Mode().Perm(); got != 0o600 {
		t.Fatalf("config file mode = %#o, want %#o", got, 0o600)
	}

	dirInfo, err := os.Stat(filepath.Dir(path))
	if err != nil {
		t.Fatalf("Stat(config dir) error = %v", err)
	}
	if got := dirInfo.Mode().Perm(); got != 0o700 {
		t.Fatalf("config dir mode = %#o, want %#o", got, 0o700)
	}

	loaded, err := LoadOrInit()
	if err != nil {
		t.Fatalf("LoadOrInit() error = %v", err)
	}

	if !reflect.DeepEqual(loaded, cfg) {
		t.Fatalf("loaded config = %+v, want %+v", loaded, cfg)
	}

	raw := mustReadJSONFile(t)
	assertTopLevelConfigShape(t, raw)
}

func mustReadJSONFile(t *testing.T) map[string]any {
	t.Helper()

	path, err := ConfigPath()
	if err != nil {
		t.Fatalf("ConfigPath() error = %v", err)
	}

	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("ReadFile(config) error = %v", err)
	}

	var got map[string]any
	if err := json.Unmarshal(data, &got); err != nil {
		t.Fatalf("Unmarshal(config json) error = %v", err)
	}

	return got
}

func assertTopLevelConfigShape(t *testing.T, got map[string]any) {
	t.Helper()

	for _, key := range []string{"active_env", "environments", "env_profiles"} {
		if _, ok := got[key]; !ok {
			t.Fatalf("config json missing top-level key %q", key)
		}
	}
	if _, ok := got["active_profile"]; ok {
		t.Fatalf("config json unexpectedly contains top-level active_profile")
	}
	if _, ok := got["profiles"]; ok {
		t.Fatalf("config json unexpectedly contains top-level profiles")
	}
	if _, ok := got["cluster_cache"]; ok {
		t.Fatalf("config json unexpectedly contains top-level cluster_cache")
	}
}

func mustMap(t *testing.T, v any) map[string]any {
	t.Helper()

	got, ok := v.(map[string]any)
	if !ok {
		t.Fatalf("value has type %T, want map[string]any", v)
	}

	return got
}
