package handlers

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

	"github.com/gin-gonic/gin"

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

// ── Workspace Test Harness ────────────────

type workspaceHarness struct {
	*testHarness
	stores    store.Stores
	userToken string
	userID    string
}

func setupWorkspaceHarness(t *testing.T) *workspaceHarness {
	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))

	// Workspace handler — nil wfs is fine for CRUD tests that don't touch filesystem
	wsH := NewWorkspaceHandler(stores, nil)
	protected.GET("/workspaces", wsH.List)
	protected.GET("/workspaces/:id", wsH.Get)

	// Git credentials — nil vault is fine for List (doesn't decrypt)
	gitCredH := NewGitCredentialHandler(stores, nil)
	protected.GET("/git-credentials", gitCredH.List)

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

	return &workspaceHarness{
		testHarness: &testHarness{router: r, t: t},
		stores:      stores,
		userToken:   userToken,
		userID:      userID,
	}
}

// seedWorkspace creates a workspace via the store and returns it.
func (h *workspaceHarness) seedWorkspace(name, ownerType, ownerID string) *models.Workspace {
	h.t.Helper()
	w := &models.Workspace{
		Name:      name,
		OwnerType: ownerType,
		OwnerID:   ownerID,
		Status:    models.WorkspaceStatusActive,
		RootPath:  "workspaces/test-" + name,
	}
	w.ID = store.NewID()
	if err := h.stores.Workspaces.Create(context.Background(), w); err != nil {
		h.t.Fatalf("seedWorkspace: %v", err)
	}
	return w
}

// ── GET /workspaces — envelope ────────────

func TestWorkspaces_List_Empty_Envelope(t *testing.T) {
	h := setupWorkspaceHarness(t)

	resp := h.request("GET", "/api/v1/workspaces", 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.Unmarshal(resp.Body.Bytes(), &body)

	// Must have "data" key
	data, ok := body["data"]
	if !ok {
		t.Fatal("GET /workspaces must return {\"data\": [...]}, missing \"data\" key")
	}

	// data must be an array (not null)
	arr, ok := data.([]interface{})
	if !ok {
		t.Fatalf("data should be an array, got %T", data)
	}
	if len(arr) != 0 {
		t.Errorf("expected empty array, got %d items", len(arr))
	}
}

func TestWorkspaces_List_WithData_Envelope(t *testing.T) {
	h := setupWorkspaceHarness(t)

	// Seed a workspace owned by the user
	h.seedWorkspace("test-ws", models.WorkspaceOwnerUser, h.userID)

	resp := h.request("GET", "/api/v1/workspaces", 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.Unmarshal(resp.Body.Bytes(), &body)

	data, ok := body["data"].([]interface{})
	if !ok {
		t.Fatal("data must be an array")
	}
	if len(data) != 1 {
		t.Fatalf("expected 1 workspace, got %d", len(data))
	}

	// Verify workspace object shape
	ws, ok := data[0].(map[string]interface{})
	if !ok {
		t.Fatal("workspace entry should be an object")
	}

	for _, key := range []string{"id", "name", "owner_type", "owner_id", "status", "created_at", "updated_at"} {
		if _, exists := ws[key]; !exists {
			t.Errorf("missing field %q in workspace object", key)
		}
	}

	if ws["name"] != "test-ws" {
		t.Errorf("name: got %v, want test-ws", ws["name"])
	}
	if ws["owner_type"] != "user" {
		t.Errorf("owner_type: got %v, want user", ws["owner_type"])
	}
}

func TestWorkspaces_List_DoesNotExposeRootPath(t *testing.T) {
	h := setupWorkspaceHarness(t)
	h.seedWorkspace("secret-ws", models.WorkspaceOwnerUser, h.userID)

	resp := h.request("GET", "/api/v1/workspaces", h.userToken, nil)
	var body map[string]interface{}
	json.Unmarshal(resp.Body.Bytes(), &body)

	data := body["data"].([]interface{})
	ws := data[0].(map[string]interface{})

	if _, exists := ws["root_path"]; exists {
		t.Error("root_path must never be exposed in API responses")
	}
}

func TestWorkspaces_List_IsolatedByUser(t *testing.T) {
	h := setupWorkspaceHarness(t)

	// Seed workspace for another user
	otherID := database.SeedTestUser(t, "other", "other@test.com")
	h.seedWorkspace("other-ws", models.WorkspaceOwnerUser, otherID)

	// Also seed one for our user
	h.seedWorkspace("my-ws", models.WorkspaceOwnerUser, h.userID)

	resp := h.request("GET", "/api/v1/workspaces", h.userToken, nil)
	var body map[string]interface{}
	json.Unmarshal(resp.Body.Bytes(), &body)

	data := body["data"].([]interface{})
	if len(data) != 1 {
		t.Fatalf("expected 1 workspace (own only), got %d", len(data))
	}

	ws := data[0].(map[string]interface{})
	if ws["name"] != "my-ws" {
		t.Errorf("should only see own workspace, got %v", ws["name"])
	}
}

// ── GET /workspaces/:id — single object ───

func TestWorkspaces_Get_Shape(t *testing.T) {
	h := setupWorkspaceHarness(t)
	w := h.seedWorkspace("detail-ws", models.WorkspaceOwnerUser, h.userID)

	resp := h.request("GET", "/api/v1/workspaces/"+w.ID, 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.Unmarshal(resp.Body.Bytes(), &body)

	// Single object — no "data" wrapper
	if _, exists := body["data"]; exists {
		t.Error("GET /:id should return object directly, not wrapped in 'data'")
	}
	if body["id"] != w.ID {
		t.Errorf("id: got %v, want %s", body["id"], w.ID)
	}
	if body["name"] != "detail-ws" {
		t.Errorf("name: got %v, want detail-ws", body["name"])
	}
}

func TestWorkspaces_Get_NotFound(t *testing.T) {
	h := setupWorkspaceHarness(t)

	resp := h.request("GET", "/api/v1/workspaces/00000000-0000-0000-0000-000000000000", h.userToken, nil)
	if resp.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", resp.Code)
	}
}

func TestWorkspaces_Get_ForbiddenForOtherUser(t *testing.T) {
	h := setupWorkspaceHarness(t)

	otherID := database.SeedTestUser(t, "stranger", "stranger@test.com")
	w := h.seedWorkspace("private-ws", models.WorkspaceOwnerUser, otherID)

	resp := h.request("GET", "/api/v1/workspaces/"+w.ID, h.userToken, nil)
	if resp.Code != http.StatusForbidden {
		t.Fatalf("expected 403 for other user's workspace, got %d", resp.Code)
	}
}

// ── GET /git-credentials — envelope ───────

func TestGitCredentials_List_Empty_Envelope(t *testing.T) {
	h := setupWorkspaceHarness(t)

	resp := h.request("GET", "/api/v1/git-credentials", 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.Unmarshal(resp.Body.Bytes(), &body)

	// Must have "data" key
	data, ok := body["data"]
	if !ok {
		t.Fatal("GET /git-credentials must return {\"data\": [...]}, missing \"data\" key")
	}

	// data must be an array (not null)
	arr, ok := data.([]interface{})
	if !ok {
		t.Fatalf("data should be an array, got %T", data)
	}
	if len(arr) != 0 {
		t.Errorf("expected empty array, got %d items", len(arr))
	}
}

// ── Auth ──────────────────────────────────

func TestWorkspaces_RequiresAuth(t *testing.T) {
	h := setupWorkspaceHarness(t)

	resp := h.request("GET", "/api/v1/workspaces", "", nil)
	if resp.Code != http.StatusUnauthorized {
		t.Fatalf("expected 401 without token, got %d", resp.Code)
	}
}

func TestGitCredentials_RequiresAuth(t *testing.T) {
	h := setupWorkspaceHarness(t)

	resp := h.request("GET", "/api/v1/git-credentials", "", nil)
	if resp.Code != http.StatusUnauthorized {
		t.Fatalf("expected 401 without token, got %d", resp.Code)
	}
}
