package openai

import (
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	edgeservice "iop/apps/edge/internal/service"
	"iop/packages/go/config"
)

func TestRoutesRequireBearerTokenWhenConfigured(t *testing.T) {
	srv := NewServer(config.EdgeOpenAIConf{
		BearerToken: "secret-token",
		Models:      []string{"model-a"},
	}, &fakeRunService{}, nil)

	for _, tc := range []struct {
		name       string
		auth       string
		wantStatus int
	}{
		{name: "missing", wantStatus: http.StatusUnauthorized},
		{name: "wrong", auth: "Bearer wrong", wantStatus: http.StatusUnauthorized},
		{name: "valid", auth: "Bearer secret-token", wantStatus: http.StatusOK},
	} {
		t.Run(tc.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
			if tc.auth != "" {
				req.Header.Set("Authorization", tc.auth)
			}
			w := httptest.NewRecorder()

			srv.routes().ServeHTTP(w, req)

			if w.Code != tc.wantStatus {
				t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
			}
		})
	}
}

func TestHealthzDoesNotRequireBearerToken(t *testing.T) {
	srv := NewServer(config.EdgeOpenAIConf{BearerToken: "secret-token"}, &fakeRunService{}, nil)
	req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
	w := httptest.NewRecorder()

	srv.routes().ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
}

func TestModelsUsesConfiguredModelsOrTarget(t *testing.T) {
	srv := NewServer(config.EdgeOpenAIConf{Target: "fallback-model"}, &fakeRunService{}, nil)
	req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
	w := httptest.NewRecorder()
	srv.handleModels(w, req)
	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d", w.Code)
	}
	if !strings.Contains(w.Body.String(), "fallback-model") {
		t.Fatalf("expected target model, got %s", w.Body.String())
	}

	srv = NewServer(config.EdgeOpenAIConf{Models: []string{"a", "b"}}, &fakeRunService{}, nil)
	w = httptest.NewRecorder()
	srv.handleModels(w, req)
	if !strings.Contains(w.Body.String(), `"id":"a"`) || !strings.Contains(w.Body.String(), `"id":"b"`) {
		t.Fatalf("expected configured models, got %s", w.Body.String())
	}
}

// TestOpenAIModelsUsesRefreshedCatalog verifies that a provider-pool catalog
// applied via SetModelCatalog (config refresh apply) is reflected in /v1/models
// on the next request, replacing the previous catalog snapshot.
func TestOpenAIModelsUsesRefreshedCatalog(t *testing.T) {
	srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil)
	srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "model-old"}})

	req := httptest.NewRequest(http.MethodGet, "/v1/models", nil)
	w := httptest.NewRecorder()
	srv.handleModels(w, req)
	if body := w.Body.String(); !strings.Contains(body, `"id":"model-old"`) {
		t.Fatalf("expected initial catalog model-old, got %s", body)
	}

	// Refresh the catalog: the next request must show the new model and not the old.
	srv.SetModelCatalog([]config.ModelCatalogEntry{{ID: "model-new"}})
	w = httptest.NewRecorder()
	srv.handleModels(w, req)
	body := w.Body.String()
	if !strings.Contains(body, `"id":"model-new"`) {
		t.Fatalf("expected refreshed catalog model-new, got %s", body)
	}
	if strings.Contains(body, `"id":"model-old"`) {
		t.Fatalf("stale catalog model-old still present after refresh, got %s", body)
	}
}

// TestOpenAIProviderPoolRouteUsesRefreshedCatalog verifies that provider-pool
// route resolution reads the refreshed catalog snapshot, so a model added by a
// config refresh is routed via the provider pool.
func TestOpenAIProviderPoolRouteUsesRefreshedCatalog(t *testing.T) {
	srv := NewServer(config.EdgeOpenAIConf{}, &fakeRunService{}, nil)
	if srv.findProviderPoolEntry("model-new") != nil {
		t.Fatal("expected no provider-pool entry before catalog is set")
	}
	srv.SetModelCatalog([]config.ModelCatalogEntry{{
		ID:        "model-new",
		Providers: map[string]string{"prov-a": "served-a"},
	}})
	dispatch, ok := srv.resolveRouteDispatch("model-new")
	if !ok || !dispatch.ProviderPool {
		t.Fatalf("expected provider-pool dispatch for refreshed model, got ok=%v dispatch=%+v", ok, dispatch)
	}
}

func TestOllamaAPIPassthrough(t *testing.T) {
	fake := &fakeRunService{
		ollamaResp: edgeservice.OllamaAPIView{
			StatusCode:  http.StatusAccepted,
			ContentType: "application/json",
			Body:        `{"ok":true}`,
		},
	}
	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", TimeoutSec: 15}, fake, nil)
	req := httptest.NewRequest(http.MethodPost, "/api/show", strings.NewReader(`{"model":"gemma4:26b"}`))
	w := httptest.NewRecorder()

	srv.handleOllamaAPI(w, req)

	if w.Code != http.StatusAccepted {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	if fake.ollamaReq.Adapter != "ollama" || fake.ollamaReq.Method != http.MethodPost || fake.ollamaReq.Path != "/api/show" {
		t.Fatalf("passthrough req mismatch: %+v", fake.ollamaReq)
	}
	if fake.ollamaReq.Body != `{"model":"gemma4:26b"}` || fake.ollamaReq.TimeoutSec != 15 {
		t.Fatalf("passthrough body/timeout mismatch: %+v", fake.ollamaReq)
	}
	if w.Body.String() != `{"ok":true}` {
		t.Fatalf("body: got %s", w.Body.String())
	}
}

func TestOllamaAPIPassthroughPreservesConfiguredTarget(t *testing.T) {
	fake := &fakeRunService{
		ollamaResp: edgeservice.OllamaAPIView{
			StatusCode:  http.StatusOK,
			ContentType: "application/json",
			Body:        `{"models":[]}`,
		},
	}
	srv := NewServer(config.EdgeOpenAIConf{Adapter: "ollama", Target: "gemma4:26b", TimeoutSec: 15}, fake, nil)
	req := httptest.NewRequest(http.MethodGet, "/api/tags", nil)
	w := httptest.NewRecorder()

	srv.handleOllamaAPI(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("status: got %d body=%s", w.Code, w.Body.String())
	}
	if fake.ollamaReq.Target != "gemma4:26b" {
		t.Fatalf("passthrough target: got %q, want gemma4:26b", fake.ollamaReq.Target)
	}
}
