package services

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	openai "github.com/sashabaranov/go-openai"
	"wa-assistant/backend/models"
)

// Local fixture only: an embedding request cannot reach an external provider.
func useEmbeddingSignatureFixture(t *testing.T, duringRequest func()) {
	t.Helper()
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var req struct {
			Model string `json:"model"`
		}
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.Model != "fixture-model-a" {
			t.Errorf("unexpected local embedding request: model=%q err=%v", req.Model, err)
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		if duringRequest != nil {
			duringRequest()
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"object":"list","model":"fixture-model-a","data":[{"object":"embedding","index":0,"embedding":[1,0]}],"usage":{"prompt_tokens":1,"total_tokens":1}}`))
	}))
	t.Cleanup(server.Close)
	cfg := openai.DefaultConfig("local-fixture-key")
	cfg.BaseURL = server.URL
	embMu.Lock()
	oldClient, oldModel, oldDims, oldEnabled := embClient, embModel, embDims, embEnabled
	embClient, embModel, embDims, embEnabled = openai.NewClientWithConfig(cfg), "fixture-model-a", 0, true
	embMu.Unlock()
	t.Cleanup(func() {
		embMu.Lock()
		embClient, embModel, embDims, embEnabled = oldClient, oldModel, oldDims, oldEnabled
		embMu.Unlock()
	})
}

func TestKnowledgeRetrievalEmbeddingSignature(t *testing.T) {
	for _, tc := range []struct {
		name, signature, question, query string
		vector                           []float32
		wantID                           bool
		wantMode                         string
	}{
		{"different_model_same_dimensions", "fixture-model-old", "Retur jaket", "administrasi pendaftaran", []float32{1, 0}, false, "none"},
		{"legacy_unknown_model", "", "Retur jaket", "administrasi pendaftaran", []float32{1, 0}, false, "none"},
		{"matching_model", "fixture-model-a", "Retur jaket", "administrasi pendaftaran", []float32{1, 0}, true, "semantic"},
		{"matching_model_wrong_dimensions", "fixture-model-a", "Retur jaket", "administrasi pendaftaran", []float32{1, 0, 0}, false, "none"},
		{"stale_vector_keeps_keyword_retrieval", "fixture-model-old", "Cara retur jaket", "retur jaket", []float32{1, 0}, true, "keyword"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			useEmbeddingSignatureFixture(t, nil)
			item := KBItem{K: models.Knowledge{ID: 1, Question: tc.question, Answer: "Hubungi admin toko.", Source: "manual", CreatedAt: time.Now(), EmbeddingModel: tc.signature}, Vec: tc.vector}
			got, mode, sim := selectKnowledgeAdvancedContext(context.Background(), tc.query, []KBItem{item})
			if (len(got) == 1) != tc.wantID || mode != tc.wantMode {
				t.Fatalf("retrieval ids=%v mode=%s sim=%v; want found=%v mode=%s", idsOf(got), mode, sim, tc.wantID, tc.wantMode)
			}
			if tc.wantMode != "semantic" && sim != 0 {
				t.Fatalf("incompatible vector counted as semantic evidence: %v", sim)
			}
		})
	}
}

func TestEmbeddingIndexKeepsRequestSignature(t *testing.T) {
	for _, kind := range []string{"knowledge", "product"} {
		t.Run(kind, func(t *testing.T) {
			db := agenticTestDB(t)
			useEmbeddingSignatureFixture(t, func() {
				embMu.Lock()
				embModel = "fixture-model-b"
				embMu.Unlock()
			})
			var signature string
			if kind == "knowledge" {
				k := models.Knowledge{AgentID: 1, Question: "Retur jaket", Answer: "Hubungi admin toko.", Active: true, ReviewStatus: "published"}
				if err := db.Create(&k).Error; err != nil {
					t.Fatal(err)
				}
				IndexKnowledge(&k)
				if err := db.First(&k, k.ID).Error; err != nil {
					t.Fatal(err)
				}
				signature = k.EmbeddingModel
			} else {
				if err := db.AutoMigrate(&models.Product{}); err != nil {
					t.Fatal(err)
				}
				p := models.Product{AgentID: 1, Name: "Jaket"}
				if err := db.Create(&p).Error; err != nil {
					t.Fatal(err)
				}
				IndexProduct(&p)
				if err := db.First(&p, p.ID).Error; err != nil {
					t.Fatal(err)
				}
				signature = p.EmbeddingModel
				t.Cleanup(func() { InvalidateProducts(1) })
			}
			if signature != "fixture-model-a" {
				t.Fatalf("in-flight config change mislabeled vector: got %q, want fixture-model-a", signature)
			}
		})
	}
}

func TestLegacyRetrievalRejectsForeignEmbeddingSignature(t *testing.T) {
	db := agenticTestDB(t)
	useEmbeddingSignatureFixture(t, nil)
	item := KBItem{K: models.Knowledge{ID: 1, Question: "Retur jaket", EmbeddingModel: "fixture-model-old"}, Vec: []float32{1, 0}}
	if rows, _ := semanticSearchRanked("administrasi pendaftaran", []KBItem{item}); len(rows) != 0 {
		t.Errorf("legacy retrieval accepted a vector from a different model: %+v", rows)
	}
	if err := db.AutoMigrate(&models.Product{}); err != nil {
		t.Fatal(err)
	}
	p := models.Product{AgentID: 1, Name: "Jaket", EmbeddingModel: "fixture-model-old", Embedding: "[1,0]"}
	if err := db.Create(&p).Error; err != nil {
		t.Fatal(err)
	}
	InvalidateProducts(1)
	t.Cleanup(func() { InvalidateProducts(1) })
	text, ids := productKnowledgeContext(1, "administrasi pendaftaran")
	if len(ids) != 0 || strings.Contains(text, "Jaket") {
		t.Fatalf("product retrieval accepted a vector from a different model: ids=%v", ids)
	}
}
