package handlers

import (
	"context"
	"database/sql"
	"encoding/json"
	"net/http"
"testing"

	"github.com/gin-gonic/gin"

	authpkg "switchboard-core/auth"
	"switchboard-core/config"
	"switchboard-core/database"
	"switchboard-core/middleware"
	"switchboard-core/store"
	postgres "switchboard-core/store/postgres"
	sqlite "switchboard-core/store/sqlite"
)

// ── Extension Test Harness ──────────────────

type extensionHarness struct {
	*testHarness
	adminToken string
	adminID    string
	userToken  string
	userID     string
	stores     store.Stores
}

func setupExtensionHarness(t *testing.T) *extensionHarness {
	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")

	// Auth (for token generation)
	auth := NewAuthHandler(cfg, stores, nil, authpkg.NewBuiltinProvider())
	api.POST("/auth/register", auth.Register)
	api.POST("/auth/login", auth.Login)

	// Public extension assets (no auth)
	extH := NewExtensionHandler(stores)
	api.GET("/extensions/:id/assets/*path", extH.ServeExtensionAsset)

	// Protected routes
	protected := api.Group("")
	protected.Use(middleware.Auth(cfg, stores.Users, userCache))

	// User extension endpoints
	protected.GET("/extensions", extH.ListUserExtensions)
	protected.POST("/extensions/:id/settings", extH.UpdateUserExtensionSettings)
	protected.GET("/extensions/:id/manifest", extH.GetExtensionManifest)
	protected.GET("/extensions/tools", extH.ListBrowserToolSchemas)

	// Admin extension endpoints
	admin := api.Group("/admin")
	admin.Use(middleware.Auth(cfg, stores.Users, userCache), middleware.RequireAdmin(stores))
	admin.GET("/extensions", extH.AdminListExtensions)
	admin.POST("/extensions", extH.AdminInstallExtension)
	admin.PUT("/extensions/:id", extH.AdminUpdateExtension)
	admin.DELETE("/extensions/:id", extH.AdminUninstallExtension)

	// Seed admin user
	adminID := seedInsertReturningID(t,
		`INSERT INTO users (username, email, password_hash, handle, auth_source) VALUES ($1, $2, $3, $4, $5) RETURNING id`,
		"ext-admin", "ext-admin@test.com", "$2a$10$test", "ext-admin", "builtin",
	)
	database.SeedEveryoneGroupMember(t, adminID)
	database.SeedAdminsGroupMember(t, adminID)
	adminToken := makeToken(adminID, "ext-admin@test.com", "admin")

	// Seed regular user
	userID := seedInsertReturningID(t,
		`INSERT INTO users (username, email, password_hash, handle, auth_source) VALUES ($1, $2, $3, $4, $5) RETURNING id`,
		"ext-user", "ext-user@test.com", "$2a$10$test", "ext-user", "builtin",
	)
	database.SeedEveryoneGroupMember(t, userID)
	userToken := makeToken(userID, "ext-user@test.com", "user")

	return &extensionHarness{
		testHarness: &testHarness{router: r, t: t},
		adminToken:  adminToken,
		adminID:     adminID,
		userToken:   userToken,
		userID:      userID,
		stores:      stores,
	}
}

// installExtension is a helper that installs an extension via the admin API.
func (h *extensionHarness) installExtension(extID, name string, extra map[string]interface{}) map[string]interface{} {
	h.t.Helper()
	body := map[string]interface{}{
		"ext_id":     extID,
		"name":       name,
		"is_enabled": true,
	}
	for k, v := range extra {
		body[k] = v
	}
	w := h.request("POST", "/api/v1/admin/extensions", h.adminToken, body)
	if w.Code != http.StatusCreated {
		h.t.Fatalf("installExtension(%s): want 201, got %d: %s", extID, w.Code, w.Body.String())
	}
	var resp map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &resp)
	data, ok := resp["data"].(map[string]interface{})
	if !ok {
		h.t.Fatalf("installExtension: response missing data wrapper")
	}
	return data
}

// ═══════════════════════════════════════════════
// Admin CRUD Lifecycle
// ═══════════════════════════════════════════════

func TestExtension_AdminCRUDLifecycle(t *testing.T) {
	h := setupExtensionHarness(t)

	// ── Install ─────────────────────────
	ext := h.installExtension("test-ext", "Test Extension", map[string]interface{}{
		"version":     "1.0.0",
		"description": "A test extension",
		"author":      "tester",
		"manifest":    map[string]interface{}{"_script": "console.log('hello')"},
	})
	extID := ext["id"].(string)
	if extID == "" {
		t.Fatal("extension should have an id")
	}
	if ext["version"].(string) != "1.0.0" {
		t.Fatalf("version mismatch: got %q", ext["version"])
	}
	if ext["tier"].(string) != "browser" {
		t.Fatalf("tier should default to browser, got %q", ext["tier"])
	}

	// ── Admin List ──────────────────────
	w := h.request("GET", "/api/v1/admin/extensions", h.adminToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("admin list: want 200, got %d", w.Code)
	}
	var listResp map[string]interface{}
	decode(w, &listResp)
	data := listResp["data"].([]interface{})
	if len(data) != 1 {
		t.Fatalf("expected 1 extension, got %d", len(data))
	}

	// ── Update ──────────────────────────
	w = h.request("PUT", "/api/v1/admin/extensions/"+extID, h.adminToken, map[string]interface{}{
		"name":    "Updated Extension",
		"version": "2.0.0",
	})
	if w.Code != http.StatusOK {
		t.Fatalf("update: want 200, got %d: %s", w.Code, w.Body.String())
	}
	var updated map[string]interface{}
	decode(w, &updated)
	updatedData := updated["data"].(map[string]interface{})
	if updatedData["title"].(string) != "Updated Extension" {
		t.Fatalf("name not updated: got %q", updatedData["title"])
	}
	if updatedData["version"].(string) != "2.0.0" {
		t.Fatalf("version not updated: got %q", updatedData["version"])
	}

	// ── Delete ──────────────────────────
	w = h.request("DELETE", "/api/v1/admin/extensions/"+extID, h.adminToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("delete: want 200, got %d", w.Code)
	}

	// Verify gone
	w = h.request("GET", "/api/v1/admin/extensions", h.adminToken, nil)
	decode(w, &listResp)
	data = listResp["data"].([]interface{})
	if len(data) != 0 {
		t.Fatalf("expected 0 extensions after delete, got %d", len(data))
	}
}

// ═══════════════════════════════════════════════
// Admin Install Validation
// ═══════════════════════════════════════════════

func TestExtension_DuplicateExtID(t *testing.T) {
	h := setupExtensionHarness(t)

	h.installExtension("dup-ext", "First", nil)

	// Second install with same ext_id → 409
	w := h.request("POST", "/api/v1/admin/extensions", h.adminToken, map[string]interface{}{
		"ext_id": "dup-ext",
		"name":   "Second",
	})
	if w.Code != http.StatusConflict {
		t.Fatalf("duplicate ext_id: want 409, got %d: %s", w.Code, w.Body.String())
	}
}

func TestExtension_InvalidTier(t *testing.T) {
	h := setupExtensionHarness(t)

	w := h.request("POST", "/api/v1/admin/extensions", h.adminToken, map[string]interface{}{
		"ext_id": "bad-tier",
		"name":   "Bad Tier",
		"tier":   "invalid",
	})
	if w.Code != http.StatusBadRequest {
		t.Fatalf("invalid tier: want 400, got %d: %s", w.Code, w.Body.String())
	}
}

func TestExtension_ValidTiers(t *testing.T) {
	h := setupExtensionHarness(t)

	for _, tier := range []string{"browser", "starlark", "sidecar"} {
		ext := h.installExtension(tier+"-ext", tier+" Extension", map[string]interface{}{
			"tier": tier,
		})
		if ext["tier"].(string) != tier {
			t.Fatalf("tier mismatch for %s: got %q", tier, ext["tier"])
		}
	}
}

func TestExtension_MissingRequiredFields(t *testing.T) {
	h := setupExtensionHarness(t)

	// Missing ext_id
	w := h.request("POST", "/api/v1/admin/extensions", h.adminToken, map[string]interface{}{
		"name": "No ExtID",
	})
	if w.Code != http.StatusBadRequest {
		t.Fatalf("missing ext_id: want 400, got %d", w.Code)
	}

	// Missing name
	w = h.request("POST", "/api/v1/admin/extensions", h.adminToken, map[string]interface{}{
		"ext_id": "no-name",
	})
	if w.Code != http.StatusBadRequest {
		t.Fatalf("missing name: want 400, got %d", w.Code)
	}
}

func TestExtension_UpdateNotFound(t *testing.T) {
	h := setupExtensionHarness(t)

	w := h.request("PUT", "/api/v1/admin/extensions/00000000-0000-0000-0000-000000000000", h.adminToken, map[string]interface{}{
		"name": "Ghost",
	})
	if w.Code != http.StatusNotFound {
		t.Fatalf("update nonexistent: want 404, got %d", w.Code)
	}
}

func TestExtension_DeleteNotFound(t *testing.T) {
	h := setupExtensionHarness(t)

	w := h.request("DELETE", "/api/v1/admin/extensions/00000000-0000-0000-0000-000000000000", h.adminToken, nil)
	if w.Code != http.StatusNotFound {
		t.Fatalf("delete nonexistent: want 404, got %d", w.Code)
	}
}

// ═══════════════════════════════════════════════
// User List + Tier Filter
// ═══════════════════════════════════════════════

func TestExtension_UserListWithTierFilter(t *testing.T) {
	h := setupExtensionHarness(t)

	h.installExtension("browser-ext", "Browser One", map[string]interface{}{"tier": "browser"})
	h.installExtension("starlark-ext", "Starlark One", map[string]interface{}{"tier": "starlark"})

	// List all
	w := h.request("GET", "/api/v1/extensions", h.userToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("list all: want 200, got %d", w.Code)
	}
	var resp map[string]interface{}
	decode(w, &resp)
	all := resp["data"].([]interface{})
	if len(all) != 2 {
		t.Fatalf("expected 2 extensions, got %d", len(all))
	}

	// Filter by tier=browser
	w = h.request("GET", "/api/v1/extensions?tier=browser", h.userToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("list filtered: want 200, got %d", w.Code)
	}
	decode(w, &resp)
	filtered := resp["data"].([]interface{})
	if len(filtered) != 1 {
		t.Fatalf("expected 1 browser extension, got %d", len(filtered))
	}
}

func TestExtension_UserListEmptyArray(t *testing.T) {
	h := setupExtensionHarness(t)

	// No extensions installed — should return [] not null
	w := h.request("GET", "/api/v1/extensions", h.userToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("list empty: want 200, got %d", w.Code)
	}
	var resp map[string]interface{}
	decode(w, &resp)
	data := resp["data"].([]interface{})
	if data == nil {
		t.Fatal("data should be [] not null")
	}
	if len(data) != 0 {
		t.Fatalf("expected 0 extensions, got %d", len(data))
	}
}

// ═══════════════════════════════════════════════
// User Settings
// ═══════════════════════════════════════════════

func TestExtension_UserSettingsUpsert(t *testing.T) {
	h := setupExtensionHarness(t)

	ext := h.installExtension("settings-ext", "Settings Test", nil)
	extID := ext["id"].(string)

	// Save settings (insert)
	w := h.request("POST", "/api/v1/extensions/"+extID+"/settings", h.userToken, map[string]interface{}{
		"settings":   map[string]interface{}{"theme": "dark"},
		"is_enabled": true,
	})
	if w.Code != http.StatusOK {
		t.Fatalf("save settings: want 200, got %d: %s", w.Code, w.Body.String())
	}

	// Update settings (upsert — same PK)
	w = h.request("POST", "/api/v1/extensions/"+extID+"/settings", h.userToken, map[string]interface{}{
		"settings": map[string]interface{}{"theme": "light", "compact": true},
	})
	if w.Code != http.StatusOK {
		t.Fatalf("update settings: want 200, got %d: %s", w.Code, w.Body.String())
	}

	// Verify via user list — settings should be merged
	w = h.request("GET", "/api/v1/extensions", h.userToken, nil)
	var resp map[string]interface{}
	decode(w, &resp)
	data := resp["data"].([]interface{})
	if len(data) != 1 {
		t.Fatalf("expected 1 extension, got %d", len(data))
	}
	extData := data[0].(map[string]interface{})
	if extData["user_settings"] == nil {
		t.Fatal("user_settings should be present after saving")
	}
}

func TestExtension_SystemExtensionCannotBeDisabled(t *testing.T) {
	h := setupExtensionHarness(t)

	ext := h.installExtension("sys-ext", "System Extension", map[string]interface{}{
		"is_system": true,
	})
	extID := ext["id"].(string)

	// Try to disable → 403
	w := h.request("POST", "/api/v1/extensions/"+extID+"/settings", h.userToken, map[string]interface{}{
		"is_enabled": false,
	})
	if w.Code != http.StatusForbidden {
		t.Fatalf("disable system ext: want 403, got %d: %s", w.Code, w.Body.String())
	}
}

func TestExtension_SettingsNotFoundExtension(t *testing.T) {
	h := setupExtensionHarness(t)

	w := h.request("POST", "/api/v1/extensions/00000000-0000-0000-0000-000000000000/settings", h.userToken, map[string]interface{}{
		"is_enabled": true,
	})
	if w.Code != http.StatusNotFound {
		t.Fatalf("settings on nonexistent ext: want 404, got %d", w.Code)
	}
}

// ═══════════════════════════════════════════════
// User Disable/Re-enable
// ═══════════════════════════════════════════════

func TestExtension_UserDisableHidesFromList(t *testing.T) {
	h := setupExtensionHarness(t)

	ext := h.installExtension("toggle-ext", "Toggle Test", nil)
	extID := ext["id"].(string)

	// Disable via user settings
	w := h.request("POST", "/api/v1/extensions/"+extID+"/settings", h.userToken, map[string]interface{}{
		"is_enabled": false,
	})
	if w.Code != http.StatusOK {
		t.Fatalf("disable: want 200, got %d", w.Code)
	}

	// User list should be empty (extension hidden)
	w = h.request("GET", "/api/v1/extensions", h.userToken, nil)
	var resp map[string]interface{}
	decode(w, &resp)
	data := resp["data"].([]interface{})
	if len(data) != 0 {
		t.Fatalf("expected 0 after disable, got %d", len(data))
	}

	// Admin list should still show it
	w = h.request("GET", "/api/v1/admin/extensions", h.adminToken, nil)
	decode(w, &resp)
	data = resp["data"].([]interface{})
	if len(data) != 1 {
		t.Fatalf("admin should still see 1, got %d", len(data))
	}

	// Re-enable
	w = h.request("POST", "/api/v1/extensions/"+extID+"/settings", h.userToken, map[string]interface{}{
		"is_enabled": true,
	})
	if w.Code != http.StatusOK {
		t.Fatalf("re-enable: want 200, got %d", w.Code)
	}

	// Should reappear
	w = h.request("GET", "/api/v1/extensions", h.userToken, nil)
	decode(w, &resp)
	data = resp["data"].([]interface{})
	if len(data) != 1 {
		t.Fatalf("expected 1 after re-enable, got %d", len(data))
	}
}

// ═══════════════════════════════════════════════
// Manifest Endpoint
// ═══════════════════════════════════════════════

func TestExtension_GetManifest(t *testing.T) {
	h := setupExtensionHarness(t)

	h.installExtension("manifest-ext", "Manifest Test", map[string]interface{}{
		"manifest": map[string]interface{}{
			"tools": []map[string]interface{}{
				{"name": "my_tool", "description": "does stuff"},
			},
		},
	})

	// Manifest endpoint uses ext_id, not UUID
	w := h.request("GET", "/api/v1/extensions/manifest-ext/manifest", h.userToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("get manifest: want 200, got %d: %s", w.Code, w.Body.String())
	}
	var resp map[string]interface{}
	decode(w, &resp)
	if resp["data"] == nil {
		t.Fatal("manifest should be in data wrapper")
	}
}

func TestExtension_GetManifestNotFound(t *testing.T) {
	h := setupExtensionHarness(t)

	w := h.request("GET", "/api/v1/extensions/nonexistent/manifest", h.userToken, nil)
	if w.Code != http.StatusNotFound {
		t.Fatalf("manifest not found: want 404, got %d", w.Code)
	}
}

// ═══════════════════════════════════════════════
// Tool Schema Endpoint
// ═══════════════════════════════════════════════

func TestExtension_ToolSchemas(t *testing.T) {
	h := setupExtensionHarness(t)

	// Install extension with tools in manifest
	h.installExtension("tool-ext", "Tool Extension", map[string]interface{}{
		"manifest": map[string]interface{}{
			"tools": []map[string]interface{}{
				{"name": "tool_a", "description": "Tool A"},
				{"name": "tool_b", "description": "Tool B"},
			},
		},
	})

	w := h.request("GET", "/api/v1/extensions/tools", h.userToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("tools: want 200, got %d: %s", w.Code, w.Body.String())
	}
	var resp map[string]interface{}
	decode(w, &resp)
	tools := resp["data"].([]interface{})
	if len(tools) != 2 {
		t.Fatalf("expected 2 tool schemas, got %d", len(tools))
	}
}

func TestExtension_ToolSchemasEmpty(t *testing.T) {
	h := setupExtensionHarness(t)

	// No extensions → empty array, not null
	w := h.request("GET", "/api/v1/extensions/tools", h.userToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("tools empty: want 200, got %d", w.Code)
	}
	var resp map[string]interface{}
	decode(w, &resp)
	tools := resp["data"].([]interface{})
	if tools == nil {
		t.Fatal("tools should be [] not null")
	}
	if len(tools) != 0 {
		t.Fatalf("expected 0 tools, got %d", len(tools))
	}
}

func TestExtension_ToolSchemasSkipsNonBrowser(t *testing.T) {
	h := setupExtensionHarness(t)

	// Starlark extension with tools should not appear in browser tool list
	h.installExtension("starlark-tool-ext", "Starlark Tool", map[string]interface{}{
		"tier": "starlark",
		"manifest": map[string]interface{}{
			"tools": []map[string]interface{}{
				{"name": "server_tool", "description": "Server-side"},
			},
		},
	})

	w := h.request("GET", "/api/v1/extensions/tools", h.userToken, nil)
	var resp map[string]interface{}
	decode(w, &resp)
	tools := resp["data"].([]interface{})
	if len(tools) != 0 {
		t.Fatalf("starlark tools should not appear: got %d", len(tools))
	}
}

// ═══════════════════════════════════════════════
// Asset Serving
// ═══════════════════════════════════════════════

func TestExtension_AssetServing(t *testing.T) {
	h := setupExtensionHarness(t)

	h.installExtension("asset-ext", "Asset Test", map[string]interface{}{
		"manifest": map[string]interface{}{
			"_script": "console.log('extension loaded');",
		},
	})

	// Asset endpoint is public (no token), uses ext_id
	w := h.request("GET", "/api/v1/extensions/asset-ext/assets/main.js", "", nil)
	if w.Code != http.StatusOK {
		t.Fatalf("asset: want 200, got %d: %s", w.Code, w.Body.String())
	}
	if ct := w.Header().Get("Content-Type"); ct != "application/javascript; charset=utf-8" {
		t.Fatalf("content-type: got %q", ct)
	}
	body := w.Body.String()
	if body != "console.log('extension loaded');" {
		t.Fatalf("script body mismatch: got %q", body)
	}
}

func TestExtension_AssetDisabledExtension(t *testing.T) {
	h := setupExtensionHarness(t)

	ext := h.installExtension("disabled-ext", "Disabled", map[string]interface{}{
		"manifest": map[string]interface{}{"_script": "nope"},
	})
	extID := ext["id"].(string)

	// Disable the extension via admin
	w := h.request("PUT", "/api/v1/admin/extensions/"+extID, h.adminToken, map[string]interface{}{
		"is_enabled": false,
	})
	if w.Code != http.StatusOK {
		t.Fatalf("disable: want 200, got %d", w.Code)
	}

	// Asset should 404
	w = h.request("GET", "/api/v1/extensions/disabled-ext/assets/main.js", "", nil)
	if w.Code != http.StatusNotFound {
		t.Fatalf("disabled asset: want 404, got %d", w.Code)
	}
}

func TestExtension_AssetNotFound(t *testing.T) {
	h := setupExtensionHarness(t)

	w := h.request("GET", "/api/v1/extensions/nonexistent/assets/main.js", "", nil)
	if w.Code != http.StatusNotFound {
		t.Fatalf("asset not found: want 404, got %d", w.Code)
	}
}

func TestExtension_AssetNoScript(t *testing.T) {
	h := setupExtensionHarness(t)

	// Install extension without _script in manifest
	h.installExtension("no-script-ext", "No Script", map[string]interface{}{
		"manifest": map[string]interface{}{"tools": []string{}},
	})

	w := h.request("GET", "/api/v1/extensions/no-script-ext/assets/main.js", "", nil)
	if w.Code != http.StatusNotFound {
		t.Fatalf("no script: want 404, got %d", w.Code)
	}
}

// ═══════════════════════════════════════════════
// Admin Auth Enforcement
// ═══════════════════════════════════════════════

func TestExtension_AdminEndpointsRequireAdmin(t *testing.T) {
	h := setupExtensionHarness(t)

	// Regular user → 403 on admin endpoints
	endpoints := []struct {
		method string
		path   string
	}{
		{"GET", "/api/v1/admin/extensions"},
		{"POST", "/api/v1/admin/extensions"},
	}
	for _, ep := range endpoints {
		w := h.request(ep.method, ep.path, h.userToken, map[string]interface{}{
			"ext_id": "nope", "name": "nope",
		})
		if w.Code != http.StatusForbidden {
			t.Fatalf("%s %s with user token: want 403, got %d", ep.method, ep.path, w.Code)
		}
	}
}

// ═══════════════════════════════════════════════
// Partial Update
// ═══════════════════════════════════════════════

func TestExtension_PartialUpdate(t *testing.T) {
	h := setupExtensionHarness(t)

	ext := h.installExtension("partial-ext", "Original Name", map[string]interface{}{
		"version":     "1.0.0",
		"description": "Original desc",
		"author":      "Original author",
	})
	extID := ext["id"].(string)

	// Update only name — other fields should stay
	w := h.request("PUT", "/api/v1/admin/extensions/"+extID, h.adminToken, map[string]interface{}{
		"name": "New Name",
	})
	if w.Code != http.StatusOK {
		t.Fatalf("partial update: want 200, got %d", w.Code)
	}
	var resp map[string]interface{}
	decode(w, &resp)
	data := resp["data"].(map[string]interface{})
	if data["title"].(string) != "New Name" {
		t.Fatalf("name not updated: got %q", data["title"])
	}
	if data["version"].(string) != "1.0.0" {
		t.Fatalf("version should be unchanged: got %q", data["version"])
	}
	if data["description"].(string) != "Original desc" {
		t.Fatalf("description should be unchanged: got %q", data["description"])
	}
	if data["author"].(string) != "Original author" {
		t.Fatalf("author should be unchanged: got %q", data["author"])
	}
}

// ═══════════════════════════════════════════════
// Store-Level Tests (forward-looking methods)
// ═══════════════════════════════════════════════

func TestExtension_StoreListEnabled(t *testing.T) {
	h := setupExtensionHarness(t)

	h.installExtension("enabled-ext", "Enabled", map[string]interface{}{"is_enabled": true})
	ext2 := h.installExtension("will-disable", "Will Disable", nil)

	// Disable one via admin
	w := h.request("PUT", "/api/v1/admin/extensions/"+ext2["id"].(string), h.adminToken, map[string]interface{}{
		"is_enabled": false,
	})
	if w.Code != http.StatusOK {
		t.Fatalf("disable: want 200, got %d", w.Code)
	}

	// ListEnabled should return only the enabled one
	ctx := context.Background()
	pkgs, err := h.stores.Packages.ListEnabledByType(ctx, "extension")
	if err != nil {
		t.Fatalf("ListEnabled: %v", err)
	}
	if len(pkgs) != 1 {
		t.Fatalf("expected 1 enabled, got %d", len(pkgs))
	}
	if pkgs[0].ID != "enabled-ext" {
		t.Fatalf("wrong extension: got %q", pkgs[0].ID)
	}
}

func TestExtension_StoreGetUserSettings(t *testing.T) {
	h := setupExtensionHarness(t)

	ext := h.installExtension("settings-store-ext", "Settings Store", nil)
	extID := ext["id"].(string)

	ctx := context.Background()

	// No settings yet → sql.ErrNoRows
	_, err := h.stores.Packages.GetUserSettings(ctx, extID, h.userID)
	if err != sql.ErrNoRows {
		t.Fatalf("expected ErrNoRows, got %v", err)
	}

	// Save settings via handler
	w := h.request("POST", "/api/v1/extensions/"+extID+"/settings", h.userToken, map[string]interface{}{
		"settings":   map[string]interface{}{"key": "value"},
		"is_enabled": true,
	})
	if w.Code != http.StatusOK {
		t.Fatalf("save: %d", w.Code)
	}

	// Now GetUserSettings should return them
	pus, err := h.stores.Packages.GetUserSettings(ctx, extID, h.userID)
	if err != nil {
		t.Fatalf("GetUserSettings: %v", err)
	}
	if !pus.IsEnabled {
		t.Fatal("should be enabled")
	}
	var settings map[string]interface{}
	json.Unmarshal(pus.Settings, &settings)
	if settings["key"] != "value" {
		t.Fatalf("settings mismatch: %v", settings)
	}
}

func TestExtension_StoreDeleteUserSettings(t *testing.T) {
	h := setupExtensionHarness(t)

	ext := h.installExtension("del-settings-ext", "Del Settings", nil)
	extID := ext["id"].(string)
	ctx := context.Background()

	// Save settings
	w := h.request("POST", "/api/v1/extensions/"+extID+"/settings", h.userToken, map[string]interface{}{
		"settings":   map[string]interface{}{"remove": "me"},
		"is_enabled": true,
	})
	if w.Code != http.StatusOK {
		t.Fatalf("save: %d", w.Code)
	}

	// Verify present
	_, err := h.stores.Packages.GetUserSettings(ctx, extID, h.userID)
	if err != nil {
		t.Fatalf("should exist: %v", err)
	}

	// Delete via store
	err = h.stores.Packages.DeleteUserSettings(ctx, extID, h.userID)
	if err != nil {
		t.Fatalf("DeleteUserSettings: %v", err)
	}

	// Verify gone
	_, err = h.stores.Packages.GetUserSettings(ctx, extID, h.userID)
	if err != sql.ErrNoRows {
		t.Fatalf("should be gone, got %v", err)
	}
}

