package handlers

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"testing"
	"time"

	"chat-switchboard/database"
	"chat-switchboard/providers"
)

// ═══════════════════════════════════════════
// Live Provider Integration Tests
// ═══════════════════════════════════════════
// These tests require:
//   - TEST_DATABASE_URL or PGHOST+PGUSER
//   - PROVIDER + PROVIDER_KEY (or legacy VENICE_API_KEY)
//
// Provider config env vars:
//   PROVIDER      — provider type: "venice", "openai", "anthropic" (default: "venice")
//   PROVIDER_KEY  — API key (falls back to VENICE_API_KEY for compat)
//   PROVIDER_URL  — endpoint override (optional, uses default for known providers)
//
// They exercise the full flow: create provider →
// fetch models → enable model → resolve → complete.
// ═══════════════════════════════════════════

// liveProviderConfig holds resolved provider settings for live tests.
type liveProviderConfig struct {
	Provider string // "venice", "openai", "anthropic"
	Key      string
	Endpoint string
}

// defaultEndpoints maps provider names to their default API endpoints.
var defaultEndpoints = map[string]string{
	"venice":     "https://api.venice.ai/api/v1",
	"openai":     "https://api.openai.com/v1",
	"anthropic":  "https://api.anthropic.com/v1",
	"openrouter": "https://openrouter.ai/api/v1",
}

// providerKeyEnvs maps provider names to their API key env var names.
var providerKeyEnvs = map[string]string{
	"venice":     "VENICE_API_KEY",
	"openai":     "OPENAI_API_KEY",
	"anthropic":  "ANTHROPIC_API_KEY",
	"openrouter": "OPENROUTER_API_KEY",
}

// requireLiveProvider resolves the primary provider config from env vars.
// Kept for backward compat — tests that only need one provider use this.
func requireLiveProvider(t *testing.T) liveProviderConfig {
	t.Helper()

	key := os.Getenv("PROVIDER_KEY")
	if key == "" {
		// Legacy fallback
		key = os.Getenv("VENICE_API_KEY")
	}
	if key == "" {
		t.Skip("PROVIDER_KEY (or VENICE_API_KEY) not set — skipping live provider test")
	}

	provider := os.Getenv("PROVIDER")
	if provider == "" {
		provider = "venice" // default
	}

	endpoint := os.Getenv("PROVIDER_URL")
	if endpoint == "" {
		var ok bool
		endpoint, ok = defaultEndpoints[provider]
		if !ok {
			t.Fatalf("PROVIDER=%q has no default endpoint — set PROVIDER_URL", provider)
		}
	}

	t.Logf("  Live provider: %s @ %s", provider, endpoint)
	return liveProviderConfig{Provider: provider, Key: key, Endpoint: endpoint}
}

// requireLiveProviders resolves all configured providers for failover tests.
// Reads LIVE_PROVIDERS (comma-separated, e.g. "venice,openai") and resolves
// API keys from {PROVIDER}_API_KEY env vars. Falls back to requireLiveProvider
// if LIVE_PROVIDERS is not set.
func requireLiveProviders(t *testing.T) []liveProviderConfig {
	t.Helper()

	list := os.Getenv("LIVE_PROVIDERS")
	if list == "" {
		// Fallback: just the primary provider
		return []liveProviderConfig{requireLiveProvider(t)}
	}

	var configs []liveProviderConfig
	for _, name := range splitTrim(list, ",") {
		keyEnv := providerKeyEnvs[name]
		if keyEnv == "" {
			keyEnv = strings.ToUpper(name) + "_API_KEY"
		}
		key := os.Getenv(keyEnv)
		if key == "" {
			t.Logf("  Skipping provider %s: %s not set", name, keyEnv)
			continue
		}
		endpoint := os.Getenv(strings.ToUpper(name) + "_API_URL")
		if endpoint == "" {
			endpoint = defaultEndpoints[name]
		}
		if endpoint == "" {
			t.Logf("  Skipping provider %s: no endpoint", name)
			continue
		}
		configs = append(configs, liveProviderConfig{Provider: name, Key: key, Endpoint: endpoint})
	}

	if len(configs) == 0 {
		t.Skip("no live providers configured — set LIVE_PROVIDERS + API keys")
	}
	t.Logf("  Live providers: %d configured", len(configs))
	return configs
}

// splitTrim splits s by sep and trims whitespace from each element.
func splitTrim(s, sep string) []string {
	parts := strings.Split(s, sep)
	var out []string
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p != "" {
			out = append(out, p)
		}
	}
	return out
}

// providerModel holds a resolved (configID, modelID) pair from a provider.
type providerModel struct {
	ConfigID string
	ModelID  string
	Provider string
}

// setupAllProviders creates configs and enables a model for each provider.
// Tolerant: if a provider fails setup, it's skipped. Fails only if zero succeed.
func setupAllProviders(t *testing.T, h *testHarness, adminToken string, providerList []liveProviderConfig) []providerModel {
	t.Helper()
	var models []providerModel
	for _, pc := range providerList {
		configID, modelID, err := trySetupProvider(h, adminToken, pc)
		if err != nil {
			t.Logf("  ⚠ %s setup failed: %v — skipping", pc.Provider, err)
			continue
		}
		t.Logf("  Provider %s ready, model %s enabled", pc.Provider, modelID)
		models = append(models, providerModel{ConfigID: configID, ModelID: modelID, Provider: pc.Provider})
	}
	if len(models) == 0 {
		t.Fatal("no providers available after setup — all failed")
	}
	return models
}

// trySetupProvider is like setupProviderWithModel but returns error instead of t.Fatal.
func trySetupProvider(h *testHarness, adminToken string, pc liveProviderConfig) (string, string, error) {
	// Create provider
	w := h.request("POST", "/api/v1/admin/configs", adminToken, map[string]interface{}{
		"name": pc.Provider + " Test", "provider": pc.Provider,
		"endpoint": pc.Endpoint, "api_key": pc.Key,
	})
	if w.Code != http.StatusCreated {
		return "", "", fmt.Errorf("create config: %d: %s", w.Code, w.Body.String())
	}
	var cfg map[string]interface{}
	decode(w, &cfg)
	configID := cfg["id"].(string)

	// Fetch models
	w = h.request("POST", "/api/v1/admin/models/fetch", adminToken,
		map[string]interface{}{"provider_config_id": configID})
	if w.Code != http.StatusOK {
		return "", "", fmt.Errorf("fetch models: %d: %s", w.Code, w.Body.String())
	}

	// Find and enable a non-reasoning model
	w = h.request("GET", "/api/v1/admin/models", adminToken, nil)
	var modelsResp map[string]interface{}
	decode(w, &modelsResp)

	var catalogID, modelID string
	var fallbackCatalogID, fallbackModelID string
	var toolCapableCatalogID, toolCapableModelID string

	for _, raw := range modelsResp["data"].([]interface{}) {
		m := raw.(map[string]interface{})
		if m["visibility"].(string) != "disabled" {
			continue
		}
		// Only pick models from this provider
		if m["provider_config_id"] != nil && m["provider_config_id"].(string) != configID {
			continue
		}
		mid := m["model_id"].(string)
		cid := m["id"].(string)
		if fallbackCatalogID == "" {
			fallbackCatalogID = cid
			fallbackModelID = mid
		}
		isReasoning := false
		hasToolCalling := false
		if caps, ok := m["capabilities"].(map[string]interface{}); ok {
			if r, exists := caps["reasoning"]; exists && r == true {
				isReasoning = true
			}
			if tc, exists := caps["tool_calling"]; exists && tc == true {
				hasToolCalling = true
			}
		}
		// Prefer non-reasoning models with tool_calling support
		if !isReasoning && hasToolCalling && toolCapableCatalogID == "" {
			toolCapableCatalogID = cid
			toolCapableModelID = mid
		}
		if !isReasoning && catalogID == "" {
			catalogID = cid
			modelID = mid
		}
	}
	// Prefer tool-capable model (server may inject tools into completion)
	if toolCapableCatalogID != "" {
		catalogID = toolCapableCatalogID
		modelID = toolCapableModelID
	}
	if catalogID == "" {
		catalogID = fallbackCatalogID
		modelID = fallbackModelID
	}
	if catalogID == "" {
		return "", "", fmt.Errorf("no disabled model found")
	}

	w = h.request("PUT", "/api/v1/admin/models/"+catalogID, adminToken,
		map[string]interface{}{"visibility": "enabled"})
	if w.Code != http.StatusOK {
		return "", "", fmt.Errorf("enable model: %d: %s", w.Code, w.Body.String())
	}
	return configID, modelID, nil
}

// tryCompletion attempts a completion against each provider/model in order.
// Returns the first successful response and the provider that worked.
// Fails only if ALL providers fail.
func tryCompletion(t *testing.T, h *testHarness, token, channelID string, models []providerModel, stream bool) (*httptest.ResponseRecorder, providerModel) {
	t.Helper()
	var lastW *httptest.ResponseRecorder
	for _, pm := range models {
		w := h.request("POST", "/api/v1/chat/completions", token, map[string]interface{}{
			"channel_id":         channelID,
			"content":            "Say ok",
			"model":              pm.ModelID,
			"provider_config_id": pm.ConfigID,
			"stream":             &stream,
			"max_tokens":         1200,
		})
		if w.Code == http.StatusOK {
			t.Logf("  ✓ Completion via %s/%s (status %d)", pm.Provider, pm.ModelID, w.Code)
			return w, pm
		}
		t.Logf("  ✗ %s/%s returned %d — trying next", pm.Provider, pm.ModelID, w.Code)
		lastW = w
	}
	// All failed
	body := ""
	if lastW != nil {
		body = lastW.Body.String()
	}
	t.Fatalf("all %d providers failed; last: %s", len(models), body)
	return nil, providerModel{} // unreachable
}

// setupProviderWithModel creates a provider config, fetches models, and enables
// the first available model. Returns (configID, enabledModelID).
func setupProviderWithModel(t *testing.T, h *testHarness, adminToken string, pc liveProviderConfig) (string, string) {
	t.Helper()

	// Create provider
	w := h.request("POST", "/api/v1/admin/configs", adminToken, map[string]interface{}{
		"name": pc.Provider + " Test", "provider": pc.Provider,
		"endpoint": pc.Endpoint, "api_key": pc.Key,
	})
	if w.Code != http.StatusCreated {
		t.Fatalf("create %s config: want 201, got %d: %s", pc.Provider, w.Code, w.Body.String())
	}
	var cfg map[string]interface{}
	decode(w, &cfg)
	configID := cfg["id"].(string)

	// Fetch models
	w = h.request("POST", "/api/v1/admin/models/fetch", adminToken,
		map[string]interface{}{"provider_config_id": configID})
	if w.Code != http.StatusOK {
		t.Fatalf("fetch models: %d: %s", w.Code, w.Body.String())
	}

	// Find and enable a model.
	// Prefer non-reasoning models: they're cheaper and don't require
	// minimum thinking budget tokens (which causes 400s with low max_tokens).
	w = h.request("GET", "/api/v1/admin/models", adminToken, nil)
	var modelsResp map[string]interface{}
	decode(w, &modelsResp)

	var catalogID, modelID string
	var fallbackCatalogID, fallbackModelID string

	for _, raw := range modelsResp["data"].([]interface{}) {
		m := raw.(map[string]interface{})
		if m["visibility"].(string) != "disabled" {
			continue
		}

		mid := m["model_id"].(string)
		cid := m["id"].(string)

		// Save first disabled model as fallback
		if fallbackCatalogID == "" {
			fallbackCatalogID = cid
			fallbackModelID = mid
		}

		// Check if this is a reasoning model — skip it if possible
		isReasoning := false
		if caps, ok := m["capabilities"].(map[string]interface{}); ok {
			if r, exists := caps["reasoning"]; exists && r == true {
				isReasoning = true
			}
		}
		if !isReasoning {
			catalogID = cid
			modelID = mid
			break
		}
	}

	// Fall back to any disabled model if all are reasoning models
	if catalogID == "" {
		catalogID = fallbackCatalogID
		modelID = fallbackModelID
	}
	if catalogID == "" {
		t.Fatal("no disabled model found to enable after fetch")
	}

	w = h.request("PUT", "/api/v1/admin/models/"+catalogID, adminToken,
		map[string]interface{}{"visibility": "enabled"})
	if w.Code != http.StatusOK {
		t.Fatalf("enable model: %d: %s", w.Code, w.Body.String())
	}

	t.Logf("  Provider %s ready, model %s enabled", configID, modelID)
	return configID, modelID
}

// TestLive_ProviderFullFlow exercises the complete admin workflow:
// create provider → fetch models → enable a model → user sees it → chat completion
func TestLive_ProviderFullFlow(t *testing.T) {
	h := setupHarness(t)
	pc := requireLiveProvider(t)
	_, adminToken := h.createAdminUser("admin", "admin@test.com")

	// ── 1. Create provider config ────────────
	t.Log("Step 1: Creating provider config")
	w := h.request("POST", "/api/v1/admin/configs", adminToken, map[string]interface{}{
		"name":     pc.Provider + " Live Test",
		"provider": pc.Provider,
		"endpoint": pc.Endpoint,
		"api_key":  pc.Key,
	})
	if w.Code != http.StatusCreated {
		t.Fatalf("create config: want 201, got %d: %s", w.Code, w.Body.String())
	}
	var configResp map[string]interface{}
	decode(w, &configResp)
	configID := configResp["id"].(string)
	t.Logf("  Created config: %s", configID)

	// ── 2. Fetch models ─────────────────────
	t.Log("Step 2: Fetching models from provider API")
	w = h.request("POST", "/api/v1/admin/models/fetch", adminToken, map[string]interface{}{
		"provider_config_id": configID,
	})
	if w.Code != http.StatusOK {
		t.Fatalf("fetch models: want 200, got %d: %s", w.Code, w.Body.String())
	}
	var fetchResp map[string]interface{}
	decode(w, &fetchResp)
	totalFetched := fetchResp["total"].(float64)
	if totalFetched < 1 {
		t.Fatalf("provider should return at least 1 model, got %.0f", totalFetched)
	}
	t.Logf("  Fetched %.0f models", totalFetched)

	// ── 3. List + enable first model ────────
	t.Log("Step 3: Enabling first available model")
	w = h.request("GET", "/api/v1/admin/models", adminToken, nil)
	var modelsResp map[string]interface{}
	decode(w, &modelsResp)
	catalogModels := modelsResp["data"].([]interface{})

	var enableID, enableModelID string
	var fallbackID, fallbackModelID string
	for _, raw := range catalogModels {
		m := raw.(map[string]interface{})
		if m["visibility"].(string) != "disabled" {
			continue
		}
		mid := m["model_id"].(string)
		cid := m["id"].(string)
		if fallbackID == "" {
			fallbackID = cid
			fallbackModelID = mid
		}
		// Prefer non-reasoning models (cheaper, no thinking budget requirement)
		isReasoning := false
		if caps, ok := m["capabilities"].(map[string]interface{}); ok {
			if r, exists := caps["reasoning"]; exists && r == true {
				isReasoning = true
			}
		}
		if !isReasoning {
			enableID = cid
			enableModelID = mid
			break
		}
	}
	if enableID == "" {
		enableID = fallbackID
		enableModelID = fallbackModelID
	}
	if enableID == "" {
		t.Fatal("no disabled model found to enable")
	}
	t.Logf("  Enabling: %s (catalog ID: %s)", enableModelID, enableID)

	w = h.request("PUT", "/api/v1/admin/models/"+enableID, adminToken,
		map[string]interface{}{"visibility": "enabled"})
	if w.Code != http.StatusOK {
		t.Fatalf("enable model: want 200, got %d: %s", w.Code, w.Body.String())
	}

	// ── 4. Admin sees enabled model ─────────
	t.Log("Step 4: Verifying models/enabled (admin)")
	w = h.request("GET", "/api/v1/models/enabled", adminToken, nil)
	var enabledResp map[string]interface{}
	decode(w, &enabledResp)
	enabledModels := enabledResp["data"].([]interface{})
	if len(enabledModels) < 1 {
		t.Fatal("models/enabled should return at least 1 model")
	}

	found := false
	for _, raw := range enabledModels {
		m := raw.(map[string]interface{})
		if m["model_id"] == enableModelID {
			found = true
			if m["config_id"] == nil || m["config_id"] == "" {
				t.Error("enabled model must have config_id")
			}
			if m["provider_name"] == nil || m["provider_name"] == "" {
				t.Error("enabled model must have provider_name")
			}
			break
		}
	}
	if !found {
		t.Errorf("model %s not found in models/enabled", enableModelID)
	}

	// ── 5. Regular user also sees model ─────
	t.Log("Step 5: Verifying models/enabled (regular user)")
	userID := database.SeedTestUser(t, "liveuser", "liveuser@test.com")
	database.TestDB.Exec("UPDATE users SET is_active = true WHERE id = "+database.PH(1), userID)
	userToken := makeToken(userID, "liveuser@test.com", "user")

	w = h.request("GET", "/api/v1/models/enabled", userToken, nil)
	var userResp map[string]interface{}
	decode(w, &userResp)
	userModels := userResp["data"].([]interface{})
	if len(userModels) < 1 {
		t.Fatal("regular user should see at least 1 enabled model")
	}
}

// TestLive_FetchModelsCapabilities verifies that model capabilities
// are correctly parsed into the catalog.
func TestLive_FetchModelsCapabilities(t *testing.T) {
	h := setupHarness(t)
	pc := requireLiveProvider(t)
	_, adminToken := h.createAdminUser("admin", "admin@test.com")

	w := h.request("POST", "/api/v1/admin/configs", adminToken, map[string]interface{}{
		"name": pc.Provider + " Caps Test", "provider": pc.Provider,
		"endpoint": pc.Endpoint, "api_key": pc.Key,
	})
	var cfg map[string]interface{}
	decode(w, &cfg)
	configID := cfg["id"].(string)

	h.request("POST", "/api/v1/admin/models/fetch", adminToken,
		map[string]interface{}{"provider_config_id": configID})

	w = h.request("GET", "/api/v1/admin/models", adminToken, nil)
	var resp map[string]interface{}
	decode(w, &resp)

	for _, raw := range resp["data"].([]interface{}) {
		m := raw.(map[string]interface{})
		caps, ok := m["capabilities"].(map[string]interface{})
		if !ok {
			t.Errorf("model %s: capabilities must be an object", m["model_id"])
			continue
		}

		// streaming should be true for most providers
		if caps["streaming"] != true {
			t.Logf("  note: model %s streaming=%v", m["model_id"], caps["streaming"])
		}

		// Verify capabilities are actual booleans
		for _, key := range []string{"streaming", "vision", "tool_calling", "reasoning"} {
			if v, exists := caps[key]; exists {
				if _, ok := v.(bool); !ok {
					t.Errorf("model %s: capability %s should be bool, got %T", m["model_id"], key, v)
				}
			}
		}
	}
}

// TestLive_ChatCompletion sends an actual non-streaming chat completion.
func TestLive_ChatCompletion(t *testing.T) {
	h := setupHarness(t)
	liveProvs := requireLiveProviders(t)
	_, adminToken := h.createAdminUser("admin", "admin@test.com")

	models := setupAllProviders(t, h, adminToken, liveProvs)

	w := h.request("POST", "/api/v1/channels", adminToken, map[string]interface{}{
		"title": "Chat Test", "type": "direct",
	})
	if w.Code != http.StatusCreated {
		t.Skipf("channel creation failed: %d %s", w.Code, w.Body.String())
	}
	var ch map[string]interface{}
	decode(w, &ch)
	channelID := ch["id"].(string)

	w, _ = tryCompletion(t, h, adminToken, channelID, models, false)
	t.Logf("  ✓ Completion succeeded: %s", w.Body.String()[:min(200, w.Body.Len())])
}

// TestLive_UsageLogging verifies that a non-streaming completion
// creates a usage_log row with token counts.
func TestLive_UsageLogging(t *testing.T) {
	h := setupHarness(t)
	liveProvs := requireLiveProviders(t)
	_, adminToken := h.createAdminUser("admin", "admin@test.com")

	models := setupAllProviders(t, h, adminToken, liveProvs)

	w := h.request("POST", "/api/v1/channels", adminToken, map[string]interface{}{
		"title": "Usage Test", "type": "direct",
	})
	if w.Code != http.StatusCreated {
		t.Skipf("channel creation failed: %d %s", w.Code, w.Body.String())
	}
	var ch map[string]interface{}
	decode(w, &ch)

	_, used := tryCompletion(t, h, adminToken, ch["id"].(string), models, false)

	var rowCount, promptTokens, completionTokens int
	err := database.TestDB.QueryRow(
		"SELECT COUNT(*), COALESCE(SUM(prompt_tokens), 0), COALESCE(SUM(completion_tokens), 0) FROM usage_log WHERE provider_config_id = "+database.PH(1),
		used.ConfigID).Scan(&rowCount, &promptTokens, &completionTokens)
	if err != nil {
		t.Fatalf("query usage_log: %v", err)
	}
	if rowCount == 0 {
		t.Fatal("usage_log should have a row after completion")
	}
	if promptTokens == 0 {
		t.Fatal("completion should report prompt tokens")
	}
	t.Logf("  ✓ Usage: %d row(s), prompt=%d completion=%d (via %s)", rowCount, promptTokens, completionTokens, used.Provider)
}

// TestLive_StreamingUsageLogging verifies streaming completions log usage.
func TestLive_StreamingUsageLogging(t *testing.T) {
	h := setupHarness(t)
	liveProvs := requireLiveProviders(t)
	_, adminToken := h.createAdminUser("admin", "admin@test.com")

	models := setupAllProviders(t, h, adminToken, liveProvs)

	w := h.request("POST", "/api/v1/channels", adminToken, map[string]interface{}{
		"title": "Stream Usage Test", "type": "direct",
	})
	if w.Code != http.StatusCreated {
		t.Skipf("channel creation failed: %d %s", w.Code, w.Body.String())
	}
	var ch map[string]interface{}
	decode(w, &ch)

	_, used := tryCompletion(t, h, adminToken, ch["id"].(string), models, true)

	var rowCount, promptTokens, completionTokens int
	err := database.TestDB.QueryRow(
		"SELECT COUNT(*), COALESCE(SUM(prompt_tokens), 0), COALESCE(SUM(completion_tokens), 0) FROM usage_log WHERE provider_config_id = "+database.PH(1),
		used.ConfigID).Scan(&rowCount, &promptTokens, &completionTokens)
	if err != nil {
		t.Fatalf("query usage_log: %v", err)
	}
	if rowCount == 0 {
		t.Fatal("usage_log should have a row after streaming completion")
	}
	if promptTokens == 0 {
		// Some providers (e.g. Venice) don't report token usage in streaming responses
		t.Skipf("streaming completion did not report prompt tokens (provider: %s) — some providers omit streaming usage", used.Provider)
	}
	t.Logf("  ✓ Streaming usage: %d row(s), prompt=%d completion=%d (via %s)", rowCount, promptTokens, completionTokens, used.Provider)
}

// TestLive_PricingFromCatalog verifies model sync populates pricing.
func TestLive_PricingFromCatalog(t *testing.T) {
	h := setupHarness(t)
	pc := requireLiveProvider(t)
	_, adminToken := h.createAdminUser("admin", "admin@test.com")

	configID, _ := setupProviderWithModel(t, h, adminToken, pc)

	var pricingCount int
	database.TestDB.QueryRow(
		"SELECT COUNT(*) FROM model_pricing WHERE provider_config_id = "+database.PH(1),
		configID).Scan(&pricingCount)

	if pricingCount == 0 {
		t.Skip("model sync did not populate pricing — provider may not support pricing data")
	}
	t.Logf("  ✓ Catalog sync populated %d pricing entries", pricingCount)

	w := h.request("GET", "/api/v1/admin/pricing", adminToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("admin pricing: %d: %s", w.Code, w.Body.String())
	}
	var entries []interface{}
	decode(w, &entries)
	if len(entries) == 0 {
		t.Error("admin pricing API should return catalog-synced entries")
	}
	t.Logf("  ✓ Admin pricing API returns %d entries", len(entries))
}

// TestLive_Embeddings tests the embeddings endpoint directly.
// Only runs for providers that support embeddings (currently Venice).
func TestLive_Embeddings(t *testing.T) {
	pc := requireLiveProvider(t)

	// Only Venice has a known embedding model for now
	if pc.Provider != "venice" {
		t.Skipf("embedding test only implemented for Venice (got %s)", pc.Provider)
	}

	provider := &providers.VeniceProvider{}
	cfg := providers.ProviderConfig{
		Endpoint: pc.Endpoint,
		APIKey:   pc.Key,
	}

	// Retry up to 3 times — Venice embedding endpoint can return transient 500s
	var resp *providers.EmbeddingResponse
	var err error
	for attempt := 1; attempt <= 3; attempt++ {
		resp, err = provider.Embed(
			context.Background(), cfg,
			providers.EmbeddingRequest{
				Model: "text-embedding-bge-m3",
				Input: []string{"test embedding"},
			},
		)
		if err == nil {
			break
		}
		t.Logf("  Embed attempt %d: %v", attempt, err)
		if attempt < 3 {
			time.Sleep(time.Duration(attempt) * 2 * time.Second)
		}
	}
	if err != nil {
		t.Skipf("Embed failed after 3 attempts (transient): %v", err)
	}
	if len(resp.Embeddings) == 0 || len(resp.Embeddings[0]) == 0 {
		t.Fatal("expected non-empty embedding vector")
	}
	t.Logf("  ✓ Embedding: %d dimensions, input_tokens=%d",
		len(resp.Embeddings[0]), resp.InputTokens)
}

// TestLive_ModelDeletion tests cleanup: delete provider removes catalog entries.
func TestLive_ModelDeletion(t *testing.T) {
	h := setupHarness(t)
	pc := requireLiveProvider(t)
	_, adminToken := h.createAdminUser("admin", "admin@test.com")

	w := h.request("POST", "/api/v1/admin/configs", adminToken, map[string]interface{}{
		"name": pc.Provider + " Delete Test", "provider": pc.Provider,
		"endpoint": pc.Endpoint, "api_key": pc.Key,
	})
	var cfg map[string]interface{}
	decode(w, &cfg)
	configID := cfg["id"].(string)

	h.request("POST", "/api/v1/admin/models/fetch", adminToken,
		map[string]interface{}{"provider_config_id": configID})

	var count int
	database.TestDB.QueryRow(
		"SELECT COUNT(*) FROM model_catalog WHERE provider_config_id = "+database.PH(1),
		configID).Scan(&count)
	if count == 0 {
		t.Fatal("catalog should have models after fetch")
	}
	t.Logf("  %d models before delete", count)

	w = h.request("DELETE", "/api/v1/admin/configs/"+configID, adminToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("delete config: want 200, got %d: %s", w.Code, w.Body.String())
	}

	database.TestDB.QueryRow(
		"SELECT COUNT(*) FROM model_catalog WHERE provider_config_id = "+database.PH(1),
		configID).Scan(&count)
	if count != 0 {
		t.Errorf("catalog should be empty after delete, got %d", count)
	}
	t.Log("  ✓ Cascade delete cleaned up catalog entries")
}

// TestLive_BYOK_AutoFetch exercises the user experience:
// user creates a BYOK provider → auto-fetch triggers → models appear.
func TestLive_BYOK_AutoFetch(t *testing.T) {
	h := setupHarness(t)
	pc := requireLiveProvider(t)
	_, adminToken := h.createAdminUser("admin", "admin@test.com")

	// Enable BYOK policy
	h.request("PUT", "/api/v1/admin/settings/allow_user_byok", adminToken,
		map[string]interface{}{"value": "true"})

	userID := database.SeedTestUser(t, "byokuser", "byokuser@test.com")
	database.TestDB.Exec("UPDATE users SET is_active = true WHERE id = "+database.PH(1), userID)
	userToken := makeToken(userID, "byokuser@test.com", "user")

	// User creates BYOK provider
	w := h.request("POST", "/api/v1/api-configs", userToken, map[string]interface{}{
		"name":     "My " + pc.Provider,
		"provider": pc.Provider,
		"endpoint": pc.Endpoint,
		"api_key":  pc.Key,
	})
	if w.Code != http.StatusCreated {
		t.Fatalf("create BYOK: want 201, got %d: %s", w.Code, w.Body.String())
	}
	var created map[string]interface{}
	decode(w, &created)
	cfgID := created["id"].(string)

	if created["warning"] != nil {
		t.Fatalf("auto-fetch should succeed, got warning: %v", created["warning"])
	}
	modelsFetched := created["models_fetched"]
	if modelsFetched == nil || modelsFetched.(float64) < 1 {
		t.Fatalf("auto-fetch should return models_fetched > 0, got: %v", modelsFetched)
	}
	t.Logf("  Auto-fetched %.0f models", modelsFetched.(float64))

	// Verify user sees personal models
	w = h.request("GET", "/api/v1/models/enabled", userToken, nil)
	var resp map[string]interface{}
	decode(w, &resp)
	userModels := resp["data"].([]interface{})

	personalCount := 0
	for _, raw := range userModels {
		m := raw.(map[string]interface{})
		if m["scope"] == "personal" {
			personalCount++
		}
	}
	if personalCount < 1 {
		t.Fatalf("user should see personal BYOK models, got %d", personalCount)
	}
	t.Logf("  ✓ User sees %d personal BYOK models", personalCount)

	// Cleanup
	h.request("DELETE", fmt.Sprintf("/api/v1/api-configs/%s", cfgID), userToken, nil)
}
