package llm

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

	"github.com/google/uuid"
	"github.com/stretchr/testify/require"
)

func TestOpenAI_Embed_ReturnsVectors(t *testing.T) {
	var gotInputs []string
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var body struct {
			Input      []string `json:"input"`
			Model      string   `json:"model"`
			Dimensions int      `json:"dimensions"`
		}
		_ = json.NewDecoder(r.Body).Decode(&body)
		gotInputs = body.Input
		require.Equal(t, PlatformEmbeddingDims, body.Dimensions)
		// Echo two 3-dim vectors (dimension count here is irrelevant to the test).
		resp := map[string]any{
			"object": "list",
			"data": []map[string]any{
				{"object": "embedding", "index": 0, "embedding": []float64{0.1, 0.2, 0.3}},
				{"object": "embedding", "index": 1, "embedding": []float64{0.4, 0.5, 0.6}},
			},
			"model": body.Model,
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(resp)
	}))
	defer srv.Close()

	c, _ := NewOpenAI("k", srv.URL)
	vecs, err := c.Embed(context.Background(), "text-embedding-3-small", []string{"alpha", "beta"})
	require.NoError(t, err)
	require.Len(t, vecs, 2)
	require.Equal(t, []float32{0.1, 0.2, 0.3}, vecs[0])
	require.Equal(t, []float32{0.4, 0.5, 0.6}, vecs[1])
	require.Equal(t, []string{"alpha", "beta"}, gotInputs)
	require.Equal(t, PlatformEmbeddingDims, c.EmbedDims("text-embedding-3-small"))
}

func TestOpenAI_Embed_RespectsResponseIndex(t *testing.T) {
	// Server returns vectors out of order; Embed must place them by Index.
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		resp := map[string]any{
			"object": "list",
			"data": []map[string]any{
				{"object": "embedding", "index": 1, "embedding": []float64{9, 9}},
				{"object": "embedding", "index": 0, "embedding": []float64{1, 1}},
			},
			"model": "m",
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(resp)
	}))
	defer srv.Close()

	c, _ := NewOpenAI("k", srv.URL)
	vecs, err := c.Embed(context.Background(), "m", []string{"a", "b"})
	require.NoError(t, err)
	require.Equal(t, []float32{1, 1}, vecs[0])
	require.Equal(t, []float32{9, 9}, vecs[1])
}

func TestOpenAI_Embed_EmptyInputs(t *testing.T) {
	c, _ := NewOpenAI("k", "")
	vecs, err := c.Embed(context.Background(), "m", nil)
	require.NoError(t, err)
	require.Nil(t, vecs)
}

func TestAnthropic_Embed_Unsupported(t *testing.T) {
	c, _ := NewAnthropic("k", "")
	_, err := c.Embed(context.Background(), "m", []string{"x"})
	require.ErrorIs(t, err, ErrEmbeddingsUnsupported)
	require.Equal(t, 0, c.EmbedDims("m"))
}

func TestFakeEmbedder_Deterministic(t *testing.T) {
	e := NewFakeEmbedder()
	v1, err := e.Embed(context.Background(), "m", []string{"hello"})
	require.NoError(t, err)
	v2, err := e.Embed(context.Background(), "m", []string{"hello"})
	require.NoError(t, err)
	require.Equal(t, v1[0], v2[0], "same input must yield identical vector")
	require.Len(t, v1[0], PlatformEmbeddingDims)

	other, err := e.Embed(context.Background(), "m", []string{"world"})
	require.NoError(t, err)
	require.NotEqual(t, v1[0], other[0], "different inputs must differ")
}

func TestFakeEmbedder_Error(t *testing.T) {
	e := &FakeEmbedder{Dims: 4, Err: errors.New("boom")}
	_, err := e.Embed(context.Background(), "m", []string{"x"})
	require.Error(t, err)
}

func TestRegistry_GetEmbedder_TypeAsserts(t *testing.T) {
	// OpenAI client implements Embedder.
	id := uuid.New()
	reg := NewRegistry(&fakeLoader{endpoints: map[uuid.UUID]fakeEndpoint{
		id: {ID: id, Provider: "openai", BaseURL: "http://localhost", APIKey: "k"},
	}})
	emb, err := reg.GetEmbedder(context.Background(), id)
	require.NoError(t, err)
	require.NotNil(t, emb)
	require.Equal(t, PlatformEmbeddingDims, emb.EmbedDims("m"))
}

func TestRegistry_GetEmbedder_AnthropicSupportedButUnsupportedEmbed(t *testing.T) {
	// Anthropic client implements Embedder (Embed returns ErrEmbeddingsUnsupported).
	id := uuid.New()
	reg := NewRegistry(&fakeLoader{endpoints: map[uuid.UUID]fakeEndpoint{
		id: {ID: id, Provider: "anthropic", BaseURL: "http://localhost", APIKey: "k"},
	}})
	emb, err := reg.GetEmbedder(context.Background(), id)
	require.NoError(t, err)
	_, eerr := emb.Embed(context.Background(), "m", []string{"x"})
	require.ErrorIs(t, eerr, ErrEmbeddingsUnsupported)
}
