package handlers

import (
	"context"
	"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"
)

// ── Dormant Test Harness ──────────────────────

type dormantHarness struct {
	*testHarness
	adminToken string
	adminID    string
	stores     store.Stores
	pkgH       *PackageHandler
}

func setupDormantHarness(t *testing.T) *dormantHarness {
	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 := NewAuthHandler(cfg, stores, nil, authpkg.NewBuiltinProvider())
	api.POST("/auth/register", auth.Register)

	pkgH := NewPackageHandler(stores)

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

	// Admin routes
	admin := api.Group("/admin")
	admin.Use(middleware.Auth(cfg, stores.Users, userCache), middleware.RequireAdmin(stores))
	admin.GET("/packages", pkgH.ListPackages)
	admin.PUT("/packages/:id/enable", pkgH.EnablePackage)
	admin.PUT("/packages/:id/disable", pkgH.DisablePackage)

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

	return &dormantHarness{
		testHarness: &testHarness{router: r, t: t},
		adminToken:  adminToken,
		adminID:     adminID,
		stores:      stores,
		pkgH:        pkgH,
	}
}

// seedPackage inserts a package directly into the DB for testing.
func (h *dormantHarness) seedPackage(id, title, pkgType, status string, enabled bool, manifest map[string]any) {
	h.t.Helper()
	ctx := context.Background()

	manifestJSON, _ := json.Marshal(manifest)

	q := dialectSQL(`INSERT INTO packages (id, title, type, version, tier, manifest, enabled, status, source)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`)

	enabledInt := 0
	if enabled {
		enabledInt = 1
	}

	_, err := database.TestDB.ExecContext(ctx, q,
		id, title, pkgType, "1.0.0", "browser", string(manifestJSON), enabledInt, status, "extension",
	)
	if err != nil {
		h.t.Fatalf("seedPackage(%s): %v", id, err)
	}
}

// ═══════════════════════════════════════════════
// Tests
// ═══════════════════════════════════════════════

// TestSetStatus_Dormant verifies the DB CHECK constraint accepts 'dormant'.
func TestSetStatus_Dormant(t *testing.T) {
	h := setupDormantHarness(t)

	h.seedPackage("status-test", "Status Test", "surface", "active", true, map[string]any{
		"id": "status-test", "title": "Status Test", "type": "surface",
	})

	err := h.stores.Packages.SetStatus(context.Background(), "status-test", "dormant")
	if err != nil {
		t.Fatalf("SetStatus(dormant) failed: %v", err)
	}

	pkg, err := h.stores.Packages.Get(context.Background(), "status-test")
	if err != nil || pkg == nil {
		t.Fatalf("Get after SetStatus failed: %v", err)
	}
	if pkg.Status != "dormant" {
		t.Errorf("status = %q, want %q", pkg.Status, "dormant")
	}
}

// TestEnableDormant_Blocked verifies the enable endpoint rejects dormant packages.
func TestEnableDormant_Blocked(t *testing.T) {
	h := setupDormantHarness(t)

	h.seedPackage("dormant-ext", "Dormant Extension", "extension", "dormant", false, map[string]any{
		"id": "dormant-ext", "title": "Dormant Extension", "type": "extension", "requires": []string{"chat"},
	})

	w := h.request("PUT", "/api/v1/admin/packages/dormant-ext/enable", h.adminToken, nil)
	if w.Code != http.StatusConflict {
		t.Fatalf("enable dormant: want 409, got %d: %s", w.Code, w.Body.String())
	}

	var resp map[string]interface{}
	decode(t, w, &resp)
	errMsg, _ := resp["error"].(string)
	if errMsg == "" {
		t.Fatal("expected error message in response")
	}
}

// TestDormantNotInSurfaces verifies dormant packages are excluded from the surfaces list.
func TestDormantNotInSurfaces(t *testing.T) {
	h := setupDormantHarness(t)

	// Active surface — should appear
	h.seedPackage("active-surface", "Active Surface", "surface", "active", true, map[string]any{
		"id": "active-surface", "title": "Active Surface", "type": "surface",
		"route": "/s/active-surface",
	})
	// Dormant surface — should NOT appear
	h.seedPackage("dormant-surface", "Dormant Surface", "full", "dormant", false, map[string]any{
		"id": "dormant-surface", "title": "Dormant Surface", "type": "full",
		"route": "/s/dormant-surface", "requires": []string{"chat"},
	})

	w := h.request("GET", "/api/v1/surfaces", h.adminToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("surfaces list: want 200, got %d: %s", w.Code, w.Body.String())
	}

	var resp map[string]interface{}
	decode(t, w, &resp)
	data, _ := resp["data"].([]interface{})

	for _, item := range data {
		m, _ := item.(map[string]interface{})
		if m["id"] == "dormant-surface" {
			t.Fatal("dormant surface should not appear in surfaces list")
		}
	}

	found := false
	for _, item := range data {
		m, _ := item.(map[string]interface{})
		if m["id"] == "active-surface" {
			found = true
		}
	}
	if !found {
		t.Fatal("active surface should appear in surfaces list")
	}
}

// TestDormantInAdminList verifies dormant packages still appear in admin package list.
func TestDormantInAdminList(t *testing.T) {
	h := setupDormantHarness(t)

	h.seedPackage("vis-active", "Visible Active", "surface", "active", true, map[string]any{
		"id": "vis-active", "title": "Visible Active", "type": "surface",
	})
	h.seedPackage("vis-dormant", "Visible Dormant", "extension", "dormant", false, map[string]any{
		"id": "vis-dormant", "title": "Visible Dormant", "type": "extension", "requires": []string{"chat"},
	})

	w := h.request("GET", "/api/v1/admin/packages", h.adminToken, nil)
	if w.Code != http.StatusOK {
		t.Fatalf("admin list: want 200, got %d", w.Code)
	}

	var resp map[string]interface{}
	decode(t, w, &resp)
	data, _ := resp["data"].([]interface{})

	ids := map[string]bool{}
	for _, item := range data {
		m, _ := item.(map[string]interface{})
		id, _ := m["id"].(string)
		ids[id] = true
	}

	if !ids["vis-active"] {
		t.Error("active package missing from admin list")
	}
	if !ids["vis-dormant"] {
		t.Error("dormant package missing from admin list")
	}
}
