package handler

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

	"code.cubecraftcreations.com/CubeCraft-Creations/Control-Center/go-backend/internal/models"
	"github.com/go-chi/chi/v5"
)

// testHandler creates a Handler wired to mock repositories for testing.
func testHandler(t *testing.T) *Handler {
	t.Helper()
	return NewHandler(
		newMockAgentRepo(),
		newMockSessionRepo(),
		newMockTaskRepo(),
		newMockProjectRepo(),
	)
}

// serveChi creates a chi.Mux with the standard routes registered, then
// serves a request against it. Returns the recorded response.
func serveChi(h *Handler, method, path, body string) *httptest.ResponseRecorder {
	w := httptest.NewRecorder()
	req := httptest.NewRequest(method, path, strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")

	r := chi.NewRouter()
	r.Route("/api", func(api chi.Router) {
		api.Route("/agents", func(agents chi.Router) {
			agents.Get("/", h.ListAgents)
			agents.Post("/", h.CreateAgent)
			agents.Get("/{id}", h.GetAgent)
			agents.Put("/{id}", h.UpdateAgent)
			agents.Delete("/{id}", h.DeleteAgent)
			agents.Get("/{id}/history", h.AgentHistory)
		})
		api.Get("/sessions", h.ListSessions)
		api.Get("/tasks", h.ListTasks)
		api.Get("/projects", h.ListProjects)
	})
	r.ServeHTTP(w, req)
	return w
}

func parseBody(t *testing.T, w *httptest.ResponseRecorder) map[string]any {
	t.Helper()
	var body map[string]any
	if err := json.NewDecoder(w.Result().Body).Decode(&body); err != nil {
		t.Fatalf("failed to decode body: %v", err)
	}
	return body
}

func parseAgent(t *testing.T, w *httptest.ResponseRecorder) models.AgentCardData {
	t.Helper()
	var a models.AgentCardData
	if err := json.NewDecoder(w.Result().Body).Decode(&a); err != nil {
		t.Fatalf("failed to decode agent: %v", err)
	}
	return a
}

func parsePaginated(t *testing.T, w *httptest.ResponseRecorder) models.PaginatedResponse {
	t.Helper()
	var pr models.PaginatedResponse
	if err := json.NewDecoder(w.Result().Body).Decode(&pr); err != nil {
		t.Fatalf("failed to decode paginated response: %v", err)
	}
	return pr
}

// ─── Agent Tests ───────────────────────────────────────────────────────────────

func TestCreateAgent_Success(t *testing.T) {
	h := testHandler(t)
	w := serveChi(h, "POST", "/api/agents", `{
		"id": "dex",
		"displayName": "Dex",
		"role": "Backend Dev",
		"status": "idle",
		"sessionKey": "sess-1",
		"channel": "discord"
	}`)

	if w.Code != http.StatusCreated {
		t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
	}

	a := parseAgent(t, w)
	if a.ID != "dex" {
		t.Errorf("expected id=dex, got %s", a.ID)
	}
	if a.Status != models.AgentStatusIdle {
		t.Errorf("expected status=idle, got %s", a.Status)
	}
}

func TestCreateAgent_Duplicate(t *testing.T) {
	h := testHandler(t)
	serveChi(h, "POST", "/api/agents", `{"id":"otto","displayName":"Otto","role":"Orchestrator","status":"active","sessionKey":"s1","channel":"discord"}`)
	w := serveChi(h, "POST", "/api/agents", `{"id":"otto","displayName":"Otto","role":"Orchestrator","status":"active","sessionKey":"s2","channel":"discord"}`)

	if w.Code != http.StatusConflict {
		t.Fatalf("expected 409, got %d", w.Code)
	}
}

func TestCreateAgent_Validation(t *testing.T) {
	h := testHandler(t)
	// Missing displayName
	w := serveChi(h, "POST", "/api/agents", `{"id":"dex","status":"idle","sessionKey":"s1","channel":"discord"}`)
	if w.Code != http.StatusUnprocessableEntity {
		t.Fatalf("expected 422, got %d", w.Code)
	}
}

func TestCreateAgent_InvalidStatus(t *testing.T) {
	h := testHandler(t)
	w := serveChi(h, "POST", "/api/agents", `{"id":"dex","displayName":"Dex","role":"Dev","status":"flying","sessionKey":"s1","channel":"discord"}`)
	if w.Code != http.StatusUnprocessableEntity {
		t.Fatalf("expected 422 for invalid status, got %d", w.Code)
	}
}

func TestGetAgent_NotFound(t *testing.T) {
	h := testHandler(t)
	w := serveChi(h, "GET", "/api/agents/nonexistent", "")
	if w.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", w.Code)
	}
}

func TestGetAgent_Success(t *testing.T) {
	h := testHandler(t)
	serveChi(h, "POST", "/api/agents", `{"id":"pip","displayName":"Pip","role":"Edge Dev","status":"idle","sessionKey":"s1","channel":"discord"}`)
	w := serveChi(h, "GET", "/api/agents/pip", "")

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}
	a := parseAgent(t, w)
	if a.DisplayName != "Pip" {
		t.Errorf("expected Pip, got %s", a.DisplayName)
	}
}

func TestListAgents(t *testing.T) {
	h := testHandler(t)
	serveChi(h, "POST", "/api/agents", `{"id":"a1","displayName":"Alpha","role":"Tester","status":"idle","sessionKey":"s1","channel":"discord"}`)
	serveChi(h, "POST", "/api/agents", `{"id":"a2","displayName":"Beta","role":"Tester","status":"active","sessionKey":"s2","channel":"discord"}`)

	w := serveChi(h, "GET", "/api/agents", "")
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}

	pr := parsePaginated(t, w)
	if pr.TotalCount != 2 {
		t.Errorf("expected totalCount=2, got %d", pr.TotalCount)
	}
}

func TestListAgents_FilterByStatus(t *testing.T) {
	h := testHandler(t)
	serveChi(h, "POST", "/api/agents", `{"id":"a1","displayName":"Alpha","role":"Tester","status":"idle","sessionKey":"s1","channel":"discord"}`)
	serveChi(h, "POST", "/api/agents", `{"id":"a2","displayName":"Beta","role":"Tester","status":"active","sessionKey":"s2","channel":"discord"}`)

	w := serveChi(h, "GET", "/api/agents?status=active", "")
	pr := parsePaginated(t, w)
	if pr.TotalCount != 2 {
		t.Errorf("expected totalCount=2 (unfiltered), got %d", pr.TotalCount)
	}
}

func TestUpdateAgent(t *testing.T) {
	h := testHandler(t)
	serveChi(h, "POST", "/api/agents", `{"id":"hex","displayName":"Hex","role":"DB Specialist","status":"idle","sessionKey":"s1","channel":"discord"}`)

	w := serveChi(h, "PUT", "/api/agents/hex", `{"status":"thinking","currentTask":"schema review"}`)
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	a := parseAgent(t, w)
	if a.Status != models.AgentStatusThinking {
		t.Errorf("expected status=thinking, got %s", a.Status)
	}
	if a.CurrentTask == nil || *a.CurrentTask != "schema review" {
		t.Errorf("expected currentTask=schema review")
	}
}

func TestUpdateAgent_NotFound(t *testing.T) {
	h := testHandler(t)
	w := serveChi(h, "PUT", "/api/agents/nope", `{"status":"idle"}`)
	if w.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", w.Code)
	}
}

func TestDeleteAgent(t *testing.T) {
	h := testHandler(t)
	serveChi(h, "POST", "/api/agents", `{"id":"temp","displayName":"Temp","role":"Temp","status":"idle","sessionKey":"s1","channel":"discord"}`)

	w := serveChi(h, "DELETE", "/api/agents/temp", "")
	if w.Code != http.StatusNoContent {
		t.Fatalf("expected 204, got %d", w.Code)
	}

	// Verify gone
	w2 := serveChi(h, "GET", "/api/agents/temp", "")
	if w2.Code != http.StatusNotFound {
		t.Fatalf("expected 404 after delete, got %d", w2.Code)
	}
}

func TestAgentHistory(t *testing.T) {
	h := testHandler(t)
	serveChi(h, "POST", "/api/agents", `{"id":"nano","displayName":"Nano","role":"Firmware","status":"idle","sessionKey":"s1","channel":"discord"}`)

	w := serveChi(h, "GET", "/api/agents/nano/history", "")
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}

	var entries []models.AgentStatusHistoryEntry
	json.NewDecoder(w.Result().Body).Decode(&entries)
	// History returns empty stub since not yet in PostgreSQL
	if entries == nil {
		t.Error("expected non-nil history slice")
	}
}

func TestAgentHistory_NotFound(t *testing.T) {
	h := testHandler(t)
	w := serveChi(h, "GET", "/api/agents/ghost/history", "")
	if w.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", w.Code)
	}
}

// ─── Session Tests ─────────────────────────────────────────────────────────═

func TestListSessions_Empty(t *testing.T) {
	h := testHandler(t)
	w := serveChi(h, "GET", "/api/sessions", "")
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}
	pr := parsePaginated(t, w)
	if pr.TotalCount != 0 {
		t.Errorf("expected 0 sessions, got %d", pr.TotalCount)
	}
}

func TestListSessions_WithData(t *testing.T) {
	h := testHandler(t)
	h.Sessions.Create(nil, models.Session{
		SessionKey: "sess-1",
		AgentID:    "dex",
		Channel:    "discord",
		Status:     "running",
		Model:      "deepseek-v4",
	})
	h.Sessions.Create(nil, models.Session{
		SessionKey: "sess-2",
		AgentID:    "otto",
		Channel:    "discord",
		Status:     "done",
		Model:      "deepseek-v4",
	})

	w := serveChi(h, "GET", "/api/sessions", "")
	pr := parsePaginated(t, w)
	if pr.TotalCount != 2 {
		t.Errorf("expected totalCount=2, got %d", pr.TotalCount)
	}
}

// ─── Task Tests ────────────────────────────────────────────────────────────────

func TestListTasks_Empty(t *testing.T) {
	h := testHandler(t)
	w := serveChi(h, "GET", "/api/tasks", "")
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}
}

func TestListTasks_WithData(t *testing.T) {
	h := testHandler(t)
	h.Tasks.Create(nil, models.Task{
		AgentID: "dex",
		Title:   "Implement CRUD API",
		Status:  models.TaskStatusRunning,
	})

	w := serveChi(h, "GET", "/api/tasks", "")
	pr := parsePaginated(t, w)
	if pr.TotalCount != 1 {
		t.Errorf("expected totalCount=1, got %d", pr.TotalCount)
	}
}

// ─── Project Tests ─────────────────────────────────────────────────────────────

func TestListProjects_Empty(t *testing.T) {
	h := testHandler(t)
	w := serveChi(h, "GET", "/api/projects", "")
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}
}

func TestListProjects_WithData(t *testing.T) {
	h := testHandler(t)
	h.Projects.Create(nil, models.Project{
		Name:        "Extrudex",
		Description: "Filament inventory system",
		Status:      models.ProjectStatusActive,
	})

	w := serveChi(h, "GET", "/api/projects", "")
	pr := parsePaginated(t, w)
	if pr.TotalCount != 1 {
		t.Errorf("expected totalCount=1, got %d", pr.TotalCount)
	}
}

// ─── Pagination Tests ─────────────────────────────────────────────────────────

func TestPagination_PageOutOfRange(t *testing.T) {
	h := testHandler(t)
	serveChi(h, "POST", "/api/agents", `{"id":"a1","displayName":"A1","role":"T","status":"idle","sessionKey":"s1","channel":"discord"}`)

	w := serveChi(h, "GET", "/api/agents?page=99", "")
	pr := parsePaginated(t, w)
	if len(pr.Data.([]any)) != 0 {
		t.Errorf("expected empty page, got %d items", len(pr.Data.([]any)))
	}
	if pr.HasMore {
		t.Error("expected HasMore=false when page is beyond data")
	}
}

func TestPagination_PageSize(t *testing.T) {
	h := testHandler(t)
	for i := range 5 {
		id := string(rune('a' + i))
		serveChi(h, "POST", "/api/agents", `{"id":"`+string(id)+`","displayName":"`+string(id)+`","role":"T","status":"idle","sessionKey":"s1","channel":"discord"}`)
	}

	w := serveChi(h, "GET", "/api/agents?pageSize=2&page=2", "")
	pr := parsePaginated(t, w)
	if pr.PageSize != 2 {
		t.Errorf("expected pageSize=2, got %d", pr.PageSize)
	}
}
