package embedding

import (
	"context"
	"encoding/json"
	"errors"
	"math"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
)

func TestFormatVector(t *testing.T) {
	got := FormatVector([]float32{0.1, float32(math.NaN()), float32(math.Inf(1)), 0.3})
	if got != "[0.1,0,0,0.3]" {
		t.Fatalf("FormatVector=%q, want %q", got, "[0.1,0,0,0.3]")
	}
}

func TestOpenAIClientEmbedText(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/v1/embeddings" {
			t.Fatalf("path=%q, want /v1/embeddings", r.URL.Path)
		}
		if auth := r.Header.Get("Authorization"); auth != "Bearer secret" {
			t.Fatalf("authorization=%q, want Bearer secret", auth)
		}
		vec := make([]float32, Dimensions)
		vec[0], vec[len(vec)-1] = 0.1, 0.3
		_ = json.NewEncoder(w).Encode(map[string]any{"data": []any{map[string]any{"embedding": vec}}})
	}))
	defer server.Close()

	client, err := NewOpenAIClient(OpenAIClientConfig{
		BaseURL: server.URL,
		APIKey:  "secret",
		Model:   "text-embedding-3-small",
	})
	if err != nil {
		t.Fatal(err)
	}
	vec, err := client.EmbedText(context.Background(), "hello")
	if err != nil {
		t.Fatal(err)
	}
	if len(vec) != Dimensions || vec[0] != 0.1 || vec[len(vec)-1] != 0.3 {
		t.Fatalf("unexpected embedding: %#v", vec)
	}
}

func TestOpenAIClientRejectsInvalidVectorContract(t *testing.T) {
	for _, tc := range []struct {
		name string
		vec  []float32
	}{
		{name: "wrong_dimensions", vec: []float32{1, 2, 3}},
		{name: "non_finite", vec: func() []float32 {
			v := make([]float32, Dimensions)
			v[17] = float32(math.NaN())
			return v
		}()},
	} {
		t.Run(tc.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
				_ = json.NewEncoder(w).Encode(map[string]any{"data": []any{map[string]any{"embedding": tc.vec}}})
			}))
			defer server.Close()
			client, err := NewOpenAIClient(OpenAIClientConfig{BaseURL: server.URL, APIKey: "secret", Model: "model"})
			if err != nil {
				t.Fatal(err)
			}
			if _, err := client.EmbedText(context.Background(), "probe"); err == nil {
				t.Fatal("EmbedText succeeded")
			}
		})
	}
}

func TestOpenAIClientEmbedTextError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusBadGateway)
		_, _ = w.Write([]byte(`{"error":{"message":"upstream unavailable"}}`))
	}))
	defer server.Close()

	client, err := NewOpenAIClient(OpenAIClientConfig{
		BaseURL: server.URL,
		APIKey:  "secret",
		Model:   "text-embedding-3-small",
	})
	if err != nil {
		t.Fatal(err)
	}
	_, err = client.EmbedText(context.Background(), "hello")
	if err == nil || err.Error() != "embedding api status 502: upstream unavailable" {
		t.Fatalf("unexpected error: %v", err)
	}
	category, status, safeReason, ok := ProviderFailure(err)
	if !ok || category != ProviderFailureUnavailable || status != http.StatusBadGateway || safeReason != "provider is unavailable" {
		t.Fatalf("failure=(%q,%d,%q,%t)", category, status, safeReason, ok)
	}
}

func TestOpenAIClientClassifiesProviderFailuresWithoutExposingDetails(t *testing.T) {
	for _, tc := range []struct {
		name       string
		status     int
		category   ProviderFailureCategory
		safeReason string
	}{
		{name: "auth", status: http.StatusUnauthorized, category: ProviderFailureAuthRejected, safeReason: "provider rejected the credentials"},
		{name: "not_found", status: http.StatusNotFound, category: ProviderFailureNotFound, safeReason: "provider endpoint or model was not found"},
		{name: "rate_limit", status: http.StatusTooManyRequests, category: ProviderFailureRateLimited, safeReason: "provider rate limit was exceeded"},
		{name: "request", status: http.StatusBadRequest, category: ProviderFailureRequestRejected, safeReason: "provider rejected the request"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
				w.WriteHeader(tc.status)
				_, _ = w.Write([]byte(`{"error":{"message":"secret provider detail"}}`))
			}))
			defer server.Close()
			client, err := NewOpenAIClient(OpenAIClientConfig{BaseURL: server.URL, APIKey: "secret", Model: "model"})
			if err != nil {
				t.Fatal(err)
			}
			_, err = client.EmbedText(context.Background(), "probe")
			if err == nil {
				t.Fatal("EmbedText succeeded")
			}
			category, status, safeReason, ok := ProviderFailure(err)
			if !ok || category != tc.category || status != tc.status || safeReason != tc.safeReason {
				t.Fatalf("failure=(%q,%d,%q,%t)", category, status, safeReason, ok)
			}
			if errors.Is(err, context.DeadlineExceeded) {
				t.Fatal("provider response was classified as timeout")
			}
			if strings.Contains(safeReason, "secret provider detail") {
				t.Fatalf("safe reason leaked provider detail: %q", safeReason)
			}
		})
	}
}
