package handlers

import (
	"encoding/json"
	"net/http"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"

	"chat-switchboard/config"
	"chat-switchboard/database"
	"chat-switchboard/middleware"
	"chat-switchboard/store"
	postgres "chat-switchboard/store/postgres"
	sqlite "chat-switchboard/store/sqlite"
)

// ── Model Prefs Test Harness ──────────────

type modelPrefsHarness struct {
	*testHarness
	userToken  string
	userID     string
	user2Token string
	user2ID    string
	configID   string // seeded provider_config for preference tests
}

func setupModelPrefsHarness(t *testing.T) *modelPrefsHarness {
	t.Helper()
	database.RequireTestDB(t)
	database.TruncateAll(t)

	cfg := &config.Config{
		JWTSecret: testJWTSecret,
		BasePath:  "",
	}

	var stores store.Stores
	if database.IsSQLite() {
		stores = sqlite.NewStores(database.TestDB)
	} else {
		stores = postgres.NewStores(database.TestDB)
	}
	userCache := middleware.NewUserStatusCache()

	r := gin.New()
	api := r.Group("/api/v1")
	protected := api.Group("")
	protected.Use(middleware.Auth(cfg, stores.Users, userCache))

	modelPrefs := NewModelPrefsHandler(stores)
	protected.GET("/models/preferences", modelPrefs.GetPreferences)
	protected.PUT("/models/preferences", modelPrefs.SetPreference)
	protected.POST("/models/preferences/bulk", modelPrefs.BulkSetPreferences)

	// Seed two users
	userID := database.SeedTestUser(t, "prefuser", "prefuser@test.com")
	database.TestDB.Exec(dialectSQL("UPDATE users SET is_active = true WHERE id = $1"), userID)
	userToken := makeToken(userID, "prefuser@test.com", "user")

	user2ID := database.SeedTestUser(t, "prefuser2", "prefuser2@test.com")
	database.TestDB.Exec(dialectSQL("UPDATE users SET is_active = true WHERE id = $1"), user2ID)
	user2Token := makeToken(user2ID, "prefuser2@test.com", "user")

	// Seed a provider config for preference targets
	configID := uuid.New().String()
	if database.IsSQLite() {
		database.TestDB.Exec(`
			INSERT INTO provider_configs (id, scope, name, provider, endpoint, key_scope)
			VALUES (?, 'global', 'Test Provider', 'openai', 'https://api.openai.com/v1', 'global')`,
			configID)
	} else {
		database.TestDB.Exec(`
			INSERT INTO provider_configs (id, scope, name, provider, endpoint, key_scope)
			VALUES ($1, 'global', 'Test Provider', 'openai', 'https://api.openai.com/v1', 'global')`,
			configID)
	}

	return &modelPrefsHarness{
		testHarness: &testHarness{router: r, t: t},
		userToken:   userToken,
		userID:      userID,
		user2Token:  user2Token,
		user2ID:     user2ID,
		configID:    configID,
	}
}

// ── GET /models/preferences — empty state ─

func TestModelPrefs_Get_Empty(t *testing.T) {
	h := setupModelPrefsHarness(t)

	resp := h.request("GET", "/api/v1/models/preferences", h.userToken, nil)
	if resp.Code != http.StatusOK {
		t.Fatalf("got %d, body: %s", resp.Code, resp.Body.String())
	}

	var body map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&body)

	data, ok := body["data"]
	if !ok {
		t.Fatal("response must have 'data' key")
	}
	arr, ok := data.([]interface{})
	if !ok {
		t.Fatal("data must be an array")
	}
	if len(arr) != 0 {
		t.Fatalf("expected empty array, got %d items", len(arr))
	}
}

// ── PUT + GET round-trip ──────────────────

func TestModelPrefs_Set_RoundTrip(t *testing.T) {
	h := setupModelPrefsHarness(t)

	// Set preference
	resp := h.request("PUT", "/api/v1/models/preferences", h.userToken, map[string]interface{}{
		"model_id":           "test-model-1",
		"provider_config_id": h.configID,
		"hidden":             true,
		"sort_order":         5,
	})
	if resp.Code != http.StatusOK {
		t.Fatalf("PUT: got %d, body: %s", resp.Code, resp.Body.String())
	}

	// Read back
	resp = h.request("GET", "/api/v1/models/preferences", h.userToken, nil)
	if resp.Code != http.StatusOK {
		t.Fatalf("GET: got %d", resp.Code)
	}

	var body map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&body)
	arr := body["data"].([]interface{})
	if len(arr) != 1 {
		t.Fatalf("expected 1 preference, got %d", len(arr))
	}

	pref := arr[0].(map[string]interface{})
	if pref["model_id"] != "test-model-1" {
		t.Errorf("model_id: got %v", pref["model_id"])
	}
	if pref["provider_config_id"] != h.configID {
		t.Errorf("provider_config_id: got %v, want %s", pref["provider_config_id"], h.configID)
	}
	if pref["hidden"] != true {
		t.Errorf("hidden: got %v, want true", pref["hidden"])
	}
	if pref["sort_order"] != float64(5) {
		t.Errorf("sort_order: got %v, want 5", pref["sort_order"])
	}
}

// ── Upsert — no duplicate rows ────────────

func TestModelPrefs_Upsert_NoDuplicate(t *testing.T) {
	h := setupModelPrefsHarness(t)

	for _, hidden := range []bool{true, false, true} {
		resp := h.request("PUT", "/api/v1/models/preferences", h.userToken, map[string]interface{}{
			"model_id":           "test-model-1",
			"provider_config_id": h.configID,
			"hidden":             hidden,
		})
		if resp.Code != http.StatusOK {
			t.Fatalf("PUT hidden=%v: got %d", hidden, resp.Code)
		}
	}

	resp := h.request("GET", "/api/v1/models/preferences", h.userToken, nil)
	var body map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&body)
	arr := body["data"].([]interface{})
	if len(arr) != 1 {
		t.Fatalf("upsert produced %d rows, want 1", len(arr))
	}

	pref := arr[0].(map[string]interface{})
	if pref["hidden"] != true {
		t.Errorf("final hidden state should be true, got %v", pref["hidden"])
	}
}

// ── Validation: missing fields ────────────

func TestModelPrefs_Set_MissingModelID(t *testing.T) {
	h := setupModelPrefsHarness(t)

	resp := h.request("PUT", "/api/v1/models/preferences", h.userToken, map[string]interface{}{
		"provider_config_id": h.configID,
		"hidden":             true,
	})
	if resp.Code != http.StatusBadRequest {
		t.Fatalf("missing model_id: want 400, got %d", resp.Code)
	}
}

func TestModelPrefs_Set_MissingProviderConfigID(t *testing.T) {
	h := setupModelPrefsHarness(t)

	resp := h.request("PUT", "/api/v1/models/preferences", h.userToken, map[string]interface{}{
		"model_id": "test-model-1",
		"hidden":   true,
	})
	if resp.Code != http.StatusBadRequest {
		t.Fatalf("missing provider_config_id: want 400, got %d", resp.Code)
	}
}

// ── Bulk set hidden ───────────────────────

func TestModelPrefs_BulkSetHidden(t *testing.T) {
	h := setupModelPrefsHarness(t)

	resp := h.request("POST", "/api/v1/models/preferences/bulk", h.userToken, map[string]interface{}{
		"entries": []map[string]string{
			{"model_id": "bulk-model-a", "provider_config_id": h.configID},
			{"model_id": "bulk-model-b", "provider_config_id": h.configID},
		},
		"hidden": true,
	})
	if resp.Code != http.StatusOK {
		t.Fatalf("bulk: got %d, body: %s", resp.Code, resp.Body.String())
	}

	var bulkResp map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&bulkResp)
	if bulkResp["count"] != float64(2) {
		t.Errorf("bulk count: got %v, want 2", bulkResp["count"])
	}

	// Verify both are hidden
	resp = h.request("GET", "/api/v1/models/preferences", h.userToken, nil)
	var body map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&body)
	arr := body["data"].([]interface{})

	hiddenCount := 0
	for _, raw := range arr {
		p := raw.(map[string]interface{})
		if p["hidden"] == true {
			hiddenCount++
		}
	}
	if hiddenCount < 2 {
		t.Errorf("expected at least 2 hidden prefs, got %d", hiddenCount)
	}
}

// ── User isolation ────────────────────────

func TestModelPrefs_UserIsolation(t *testing.T) {
	h := setupModelPrefsHarness(t)

	// User 1 sets a preference
	resp := h.request("PUT", "/api/v1/models/preferences", h.userToken, map[string]interface{}{
		"model_id":           "isolated-model",
		"provider_config_id": h.configID,
		"hidden":             true,
	})
	if resp.Code != http.StatusOK {
		t.Fatalf("user1 PUT: got %d", resp.Code)
	}

	// User 2 should see empty preferences
	resp = h.request("GET", "/api/v1/models/preferences", h.user2Token, nil)
	if resp.Code != http.StatusOK {
		t.Fatalf("user2 GET: got %d", resp.Code)
	}

	var body map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&body)
	arr := body["data"].([]interface{})
	if len(arr) != 0 {
		t.Fatalf("user2 should have 0 prefs, got %d", len(arr))
	}
}

// ── Shape validation ──────────────────────

func TestModelPrefs_ResponseShape(t *testing.T) {
	h := setupModelPrefsHarness(t)

	// Create a preference so we have something to inspect
	h.request("PUT", "/api/v1/models/preferences", h.userToken, map[string]interface{}{
		"model_id":           "shape-model",
		"provider_config_id": h.configID,
		"hidden":             false,
		"sort_order":         0,
	})

	resp := h.request("GET", "/api/v1/models/preferences", h.userToken, nil)
	var body map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&body)
	arr := body["data"].([]interface{})
	if len(arr) == 0 {
		t.Fatal("expected at least 1 preference for shape check")
	}

	pref := arr[0].(map[string]interface{})
	requiredFields := []string{"id", "user_id", "model_id", "provider_config_id", "hidden", "sort_order", "created_at", "updated_at"}
	for _, f := range requiredFields {
		if _, ok := pref[f]; !ok {
			t.Errorf("missing required field %q in preference response", f)
		}
	}

	// id, user_id, model_id, provider_config_id should be non-empty strings
	for _, f := range []string{"id", "user_id", "model_id", "provider_config_id"} {
		v, _ := pref[f].(string)
		if v == "" {
			t.Errorf("field %q should be a non-empty string, got %v", f, pref[f])
		}
	}
}

// ── Different provider_config_id = different entry ──

func TestModelPrefs_SameModel_DifferentProvider(t *testing.T) {
	h := setupModelPrefsHarness(t)

	// Seed a second provider config
	config2ID := uuid.New().String()
	database.TestDB.Exec(dialectSQL(
		"INSERT INTO provider_configs (id, scope, name, provider, endpoint, key_scope) VALUES ($1, 'global', 'Test Provider 2', 'anthropic', 'https://api.anthropic.com/v1', 'global')"),
		config2ID)

	// Set preference for same model under two providers
	h.request("PUT", "/api/v1/models/preferences", h.userToken, map[string]interface{}{
		"model_id":           "dual-provider-model",
		"provider_config_id": h.configID,
		"hidden":             true,
	})
	h.request("PUT", "/api/v1/models/preferences", h.userToken, map[string]interface{}{
		"model_id":           "dual-provider-model",
		"provider_config_id": config2ID,
		"hidden":             false,
	})

	// Should have two distinct entries
	resp := h.request("GET", "/api/v1/models/preferences", h.userToken, nil)
	var body map[string]interface{}
	json.NewDecoder(resp.Body).Decode(&body)
	arr := body["data"].([]interface{})

	matches := 0
	for _, raw := range arr {
		p := raw.(map[string]interface{})
		if p["model_id"] == "dual-provider-model" {
			matches++
		}
	}
	if matches != 2 {
		t.Fatalf("same model, different providers: expected 2 entries, got %d", matches)
	}
}
