package config

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"slices"
	"testing"
)

func TestBuildModelFetchURLs(t *testing.T) {
	tests := []struct {
		name     string
		base     string
		override string
		want     []string
	}{
		{
			name: "root endpoint keeps legacy models path first",
			base: "https://api.deepseek.com",
			want: []string{"https://api.deepseek.com/models", "https://api.deepseek.com/v1/models"},
		},
		{
			name: "versioned endpoint uses models under version",
			base: "https://api.example.com/v1",
			want: []string{"https://api.example.com/v1/models"},
		},
		{
			name: "non-v1 version keeps v1 fallback",
			base: "https://open.bigmodel.cn/api/coding/paas/v4",
			want: []string{
				"https://open.bigmodel.cn/api/coding/paas/v4/models",
				"https://open.bigmodel.cn/api/coding/paas/v4/v1/models",
			},
		},
		{
			name: "anthropic compatible subpath adds root candidates",
			base: "https://api.deepseek.com/anthropic",
			want: []string{
				"https://api.deepseek.com/anthropic/models",
				"https://api.deepseek.com/anthropic/v1/models",
				"https://api.deepseek.com/models",
				"https://api.deepseek.com/v1/models",
			},
		},
		{
			name:     "override wins",
			base:     "https://api.deepseek.com",
			override: "https://api.deepseek.com/custom/models",
			want:     []string{"https://api.deepseek.com/custom/models"},
		},
		{
			name:     "third-party override keeps exact query and slash",
			base:     "https://api.deepseek.com",
			override: "https://api.deepseek.com/custom/models/?token=1",
			want:     []string{"https://api.deepseek.com/custom/models/?token=1"},
		},
		{
			name: "tokenrhythm missing v1 base is unique canonical models",
			base: "https://tokenrhythm.studio",
			want: []string{"https://tokenrhythm.studio/v1/models"},
		},
		{
			name: "tokenrhythm correct base is unique canonical models",
			base: "https://tokenrhythm.studio/v1",
			want: []string{"https://tokenrhythm.studio/v1/models"},
		},
		{
			name: "tokenrhythm chat url as base is unique canonical models",
			base: "https://tokenrhythm.studio/v1/chat/completions",
			want: []string{"https://tokenrhythm.studio/v1/models"},
		},
		{
			name:     "tokenrhythm wrong models override is unique canonical models",
			base:     "https://example.invalid/v1",
			override: "https://tokenrhythm.studio/v1/v1/models/",
			want:     []string{"https://tokenrhythm.studio/v1/models"},
		},
		{
			name:     "tokenrhythm unknown override stays exact",
			base:     "https://tokenrhythm.studio/v1",
			override: "https://tokenrhythm.studio/openai/models",
			want:     []string{"https://tokenrhythm.studio/openai/models"},
		},
		{
			name: "stepfun anthropic-docs base is unique canonical models",
			base: "https://api.stepfun.com/step_plan",
			want: []string{"https://api.stepfun.com/step_plan/v1/models"},
		},
		{
			name: "stepfun correct base is unique canonical models",
			base: "https://api.stepfun.com/step_plan/v1",
			want: []string{"https://api.stepfun.com/step_plan/v1/models"},
		},
		{
			name: "stepfun global host base is unique canonical models",
			base: "https://api.stepfun.ai/step_plan",
			want: []string{"https://api.stepfun.ai/step_plan/v1/models"},
		},
		{
			name: "stepfun standard api root keeps legacy candidates",
			base: "https://api.stepfun.com",
			want: []string{"https://api.stepfun.com/models", "https://api.stepfun.com/v1/models"},
		},
		{
			name:     "stepfun models override is unique canonical models",
			base:     "https://example.invalid/v1",
			override: "https://api.stepfun.com/step_plan",
			want:     []string{"https://api.stepfun.com/step_plan/v1/models"},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := BuildModelFetchURLs(tt.base, tt.override)
			if err != nil {
				t.Fatalf("BuildModelFetchURLs: %v", err)
			}
			if len(got) != len(tt.want) {
				t.Fatalf("got %v, want %v", got, tt.want)
			}
			for i := range got {
				if got[i] != tt.want[i] {
					t.Fatalf("got %v, want %v", got, tt.want)
				}
			}
		})
	}
}

func TestProviderFetchModelsFallsBackToV1Models(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/models" {
			http.NotFound(w, r)
			return
		}
		if r.URL.Path != "/v1/models" {
			t.Fatalf("unexpected path %s", r.URL.Path)
		}
		if r.Header.Get("Authorization") != "Bearer test-key" {
			http.Error(w, "bad key", http.StatusUnauthorized)
			return
		}
		_ = json.NewEncoder(w).Encode(map[string]any{
			"data": []map[string]string{{"id": "model-b"}, {"id": "model-a"}},
		})
	}))
	defer srv.Close()

	p := ProviderEntry{Name: "test", BaseURL: srv.URL, APIKeyEnv: "FETCH_MODELS_TEST_KEY", resolvedAPIKey: "test-key"}
	got, err := p.FetchModels(context.Background())
	if err != nil {
		t.Fatalf("FetchModels: %v", err)
	}
	if len(got) != 2 || got[0] != "model-a" || got[1] != "model-b" {
		t.Fatalf("got %v, want [model-a model-b]", got)
	}
}

func TestProviderFetchModelsContinuesAfterRootAuthFailure(t *testing.T) {
	var paths []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		paths = append(paths, r.URL.Path)
		switch r.URL.Path {
		case "/models":
			http.Error(w, `{"error":"wrong endpoint"}`, http.StatusUnauthorized)
		case "/v1/models":
			if r.Header.Get("Authorization") != "Bearer test-key" {
				http.Error(w, "bad key", http.StatusUnauthorized)
				return
			}
			_ = json.NewEncoder(w).Encode(map[string]any{
				"data": []map[string]string{{"id": "model-a"}},
			})
		default:
			t.Fatalf("unexpected path %s", r.URL.Path)
		}
	}))
	defer srv.Close()

	p := ProviderEntry{Name: "test", BaseURL: srv.URL, APIKeyEnv: "FETCH_MODELS_TEST_KEY", resolvedAPIKey: "test-key"}
	got, err := p.FetchModels(context.Background())
	if err != nil {
		t.Fatalf("FetchModels: %v", err)
	}
	if len(got) != 1 || got[0] != "model-a" {
		t.Fatalf("got %v, want [model-a]", got)
	}
	if len(paths) != 2 || paths[0] != "/models" || paths[1] != "/v1/models" {
		t.Fatalf("paths = %v, want [/models /v1/models]", paths)
	}
}

func TestProviderFetchModelsUsesSetupProbeEnv(t *testing.T) {
	const key = "FETCH_MODELS_PROBE_KEY"
	t.Setenv(key, "probe-key")
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Authorization") != "Bearer probe-key" {
			http.Error(w, "bad key", http.StatusUnauthorized)
			return
		}
		_ = json.NewEncoder(w).Encode(map[string]any{
			"data": []map[string]string{{"id": "probe-model"}},
		})
	}))
	defer srv.Close()

	p := ProviderEntry{Name: "probe", BaseURL: srv.URL, APIKeyEnv: key}
	p.ResolveAPIKeyFromProcessEnvForProbe()
	got, err := p.FetchModels(context.Background())
	if err != nil {
		t.Fatalf("FetchModels: %v", err)
	}
	if len(got) != 1 || got[0] != "probe-model" {
		t.Fatalf("models = %v, want [probe-model]", got)
	}
}

func TestProviderFetchModelsAllowsNoAuthEndpoint(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Header.Get("Authorization") != "" {
			http.Error(w, "unexpected auth header", http.StatusBadRequest)
			return
		}
		_ = json.NewEncoder(w).Encode(map[string]any{
			"data": []map[string]string{{"id": "local-b"}, {"id": "local-a"}},
		})
	}))
	defer srv.Close()

	p := ProviderEntry{Name: "local", BaseURL: srv.URL}
	got, err := p.FetchModels(context.Background())
	if err != nil {
		t.Fatalf("FetchModels no-auth: %v", err)
	}
	if len(got) != 2 || got[0] != "local-a" || got[1] != "local-b" {
		t.Fatalf("got %v, want [local-a local-b]", got)
	}
}

func TestProviderFetchModelsUsesAnthropicAuthMode(t *testing.T) {
	tests := []struct {
		name       string
		authHeader bool
		assertAuth func(t *testing.T, r *http.Request)
	}{
		{
			name:       "x-api-key",
			authHeader: false,
			assertAuth: func(t *testing.T, r *http.Request) {
				t.Helper()
				if got := r.Header.Get("x-api-key"); got != "anthropic-key" {
					t.Fatalf("x-api-key = %q, want anthropic-key", got)
				}
				if got := r.Header.Get("Authorization"); got != "" {
					t.Fatalf("Authorization = %q, want omitted", got)
				}
			},
		},
		{
			name:       "bearer",
			authHeader: true,
			assertAuth: func(t *testing.T, r *http.Request) {
				t.Helper()
				if got := r.Header.Get("Authorization"); got != "Bearer anthropic-key" {
					t.Fatalf("Authorization = %q, want Bearer anthropic-key", got)
				}
				if got := r.Header.Get("x-api-key"); got != "" {
					t.Fatalf("x-api-key = %q, want omitted", got)
				}
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.URL.Path != "/anthropic/models" {
					t.Fatalf("unexpected path %s", r.URL.Path)
				}
				tt.assertAuth(t, r)
				_ = json.NewEncoder(w).Encode(map[string]any{
					"data": []map[string]string{{"id": "anthropic-model"}},
				})
			}))
			defer srv.Close()

			p := ProviderEntry{
				Name:           "anthropic-compatible",
				Kind:           "anthropic",
				BaseURL:        srv.URL + "/anthropic",
				APIKeyEnv:      "ANTHROPIC_COMPATIBLE_KEY",
				AuthHeader:     tt.authHeader,
				resolvedAPIKey: "anthropic-key",
			}
			got, err := p.FetchModels(context.Background())
			if err != nil {
				t.Fatalf("FetchModels: %v", err)
			}
			if len(got) != 1 || got[0] != "anthropic-model" {
				t.Fatalf("got %v, want [anthropic-model]", got)
			}
		})
	}
}

func TestProviderFetchModelsFiltersOfficialOpenCodeGoCatalogByWireFormat(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_ = json.NewEncoder(w).Encode(map[string]any{
			"data": []map[string]string{
				{"id": "grok-4.5"},
				{"id": "qwen3.7-plus"},
				{"id": "minimax-m3"},
				{"id": "glm-5.2"},
			},
		})
	}))
	defer srv.Close()

	p := ProviderEntry{
		Name:      "opencode-go-anthropic",
		Kind:      "anthropic",
		BaseURL:   "https://opencode.ai/zen/go",
		ModelsURL: srv.URL,
	}
	got, err := p.FetchModels(context.Background())
	if err != nil {
		t.Fatalf("FetchModels: %v", err)
	}
	want := []string{"minimax-m3", "qwen3.7-plus"}
	if !slices.Equal(got, want) {
		t.Fatalf("models = %v, want %v", got, want)
	}
}
