package handlers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/service"
)

func TestWorkHandler_Fail(t *testing.T) {
	mockQueue := newMockWorkQueue()
	workService := service.NewWorkService(mockQueue)
	handler := NewWorkHandler(workService)

	// Pre-populate a running task
	mockQueue.tasks["task-1"] = &domain.WorkTask{
		ID:         "task-1",
		ProjectID:  "test-project",
		Type:       domain.WorkTaskTypeBuild,
		Status:     domain.WorkTaskStatusRunning,
		WorkerID:   "worker-1",
		MaxRetries: 3,
		CreatedAt:  time.Now(),
	}

	router := chi.NewRouter()
	router.Use(testAdminAuth)
	handler.Mount(router)

	tests := []struct {
		name       string
		taskID     string
		body       FailWorkRequest
		wantStatus int
	}{
		{
			name:       "valid_fail",
			taskID:     "task-1",
			body:       FailWorkRequest{Error: "Build failed: npm error"},
			wantStatus: http.StatusOK,
		},
		{
			name:       "missing_error",
			taskID:     "task-1",
			body:       FailWorkRequest{},
			wantStatus: http.StatusBadRequest,
		},
		{
			name:       "task_not_found",
			taskID:     "nonexistent",
			body:       FailWorkRequest{Error: "Failed"},
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			body, _ := json.Marshal(tt.body)
			req := httptest.NewRequest(http.MethodPost, "/work/"+tt.taskID+"/fail", bytes.NewReader(body))
			req.Header.Set("Content-Type", "application/json")
			rec := httptest.NewRecorder()

			router.ServeHTTP(rec, req)

			if rec.Code != tt.wantStatus {
				t.Errorf("got status %d, want %d; body: %s", rec.Code, tt.wantStatus, rec.Body.String())
			}
		})
	}
}

func TestWorkHandler_Cancel(t *testing.T) {
	mockQueue := newMockWorkQueue()
	workService := service.NewWorkService(mockQueue)
	handler := NewWorkHandler(workService)

	// Pre-populate tasks
	mockQueue.tasks["pending-task"] = &domain.WorkTask{
		ID:        "pending-task",
		ProjectID: "test-project",
		Type:      domain.WorkTaskTypeBuild,
		Status:    domain.WorkTaskStatusPending,
		CreatedAt: time.Now(),
	}
	mockQueue.tasks["running-task"] = &domain.WorkTask{
		ID:        "running-task",
		ProjectID: "test-project",
		Type:      domain.WorkTaskTypeBuild,
		Status:    domain.WorkTaskStatusRunning,
		CreatedAt: time.Now(),
	}

	router := chi.NewRouter()
	router.Use(testAdminAuth)
	handler.Mount(router)

	tests := []struct {
		name       string
		taskID     string
		wantStatus int
	}{
		{
			name:       "cancel_pending_task",
			taskID:     "pending-task",
			wantStatus: http.StatusOK,
		},
		{
			name:       "cancel_running_task_fails",
			taskID:     "running-task",
			wantStatus: http.StatusNotFound, // Can only cancel pending tasks
		},
		{
			name:       "task_not_found",
			taskID:     "nonexistent",
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodPost, "/work/"+tt.taskID+"/cancel", nil)
			rec := httptest.NewRecorder()

			router.ServeHTTP(rec, req)

			if rec.Code != tt.wantStatus {
				t.Errorf("got status %d, want %d; body: %s", rec.Code, tt.wantStatus, rec.Body.String())
			}
		})
	}
}

func TestWorkHandler_GetTask(t *testing.T) {
	mockQueue := newMockWorkQueue()
	workService := service.NewWorkService(mockQueue)
	handler := NewWorkHandler(workService)

	// Pre-populate a task
	mockQueue.tasks["task-1"] = &domain.WorkTask{
		ID:        "task-1",
		ProjectID: "test-project",
		Type:      domain.WorkTaskTypeBuild,
		Status:    domain.WorkTaskStatusRunning,
		Spec: map[string]any{
			"prompt": "Build it",
		},
		CreatedAt: time.Now(),
	}

	router := chi.NewRouter()
	router.Use(testAdminAuth)
	handler.Mount(router)

	tests := []struct {
		name       string
		taskID     string
		wantStatus int
	}{
		{
			name:       "get_existing_task",
			taskID:     "task-1",
			wantStatus: http.StatusOK,
		},
		{
			name:       "task_not_found",
			taskID:     "nonexistent",
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/work/"+tt.taskID, nil)
			rec := httptest.NewRecorder()

			router.ServeHTTP(rec, req)

			if rec.Code != tt.wantStatus {
				t.Errorf("got status %d, want %d; body: %s", rec.Code, tt.wantStatus, rec.Body.String())
			}
		})
	}
}

func TestWorkHandler_ListByProject(t *testing.T) {
	mockQueue := newMockWorkQueue()
	workService := service.NewWorkService(mockQueue)
	handler := NewWorkHandler(workService)

	// Pre-populate tasks
	mockQueue.tasks["task-1"] = &domain.WorkTask{
		ID:        "task-1",
		ProjectID: "project-a",
		Type:      domain.WorkTaskTypeBuild,
		Status:    domain.WorkTaskStatusPending,
		CreatedAt: time.Now(),
	}
	mockQueue.tasks["task-2"] = &domain.WorkTask{
		ID:        "task-2",
		ProjectID: "project-a",
		Type:      domain.WorkTaskTypeTest,
		Status:    domain.WorkTaskStatusCompleted,
		CreatedAt: time.Now(),
	}
	mockQueue.tasks["task-3"] = &domain.WorkTask{
		ID:        "task-3",
		ProjectID: "project-b",
		Type:      domain.WorkTaskTypeDeploy,
		Status:    domain.WorkTaskStatusRunning,
		CreatedAt: time.Now(),
	}

	router := chi.NewRouter()
	router.Use(testAdminAuth)
	handler.Mount(router)

	t.Run("list_all_for_project", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/work/projects/project-a", nil)
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("got status %d, want %d; body: %s", rec.Code, http.StatusOK, rec.Body.String())
		}

		var resp map[string]any
		if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
			t.Fatalf("failed to unmarshal: %v", err)
		}
		data := resp["data"].(map[string]any)
		total := int(data["total"].(float64))
		if total != 2 {
			t.Errorf("got %d tasks, want 2", total)
		}
		// Verify pagination metadata is present
		if _, ok := data["limit"]; !ok {
			t.Error("expected limit in response")
		}
		if _, ok := data["offset"]; !ok {
			t.Error("expected offset in response")
		}
	})

	t.Run("list_with_status_filter", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/work/projects/project-a?status=pending", nil)
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("got status %d, want %d", rec.Code, http.StatusOK)
		}

		var resp map[string]any
		if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
			t.Fatalf("failed to unmarshal: %v", err)
		}
		data := resp["data"].(map[string]any)
		total := int(data["total"].(float64))
		if total != 1 {
			t.Errorf("got %d tasks, want 1", total)
		}
	})

	t.Run("list_with_pagination", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/work/projects/project-a?limit=1&offset=0", nil)
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("got status %d, want %d", rec.Code, http.StatusOK)
		}

		var resp map[string]any
		if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
			t.Fatalf("failed to unmarshal: %v", err)
		}
		data := resp["data"].(map[string]any)

		// Total should reflect all matching tasks
		total := int(data["total"].(float64))
		if total != 2 {
			t.Errorf("got total=%d, want 2", total)
		}

		// But tasks returned should be limited
		tasks := data["tasks"].([]any)
		if len(tasks) != 1 {
			t.Errorf("got %d tasks returned, want 1", len(tasks))
		}

		// Verify limit/offset are reflected
		if int(data["limit"].(float64)) != 1 {
			t.Errorf("got limit=%v, want 1", data["limit"])
		}
		if int(data["offset"].(float64)) != 0 {
			t.Errorf("got offset=%v, want 0", data["offset"])
		}
	})

	t.Run("invalid_limit", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/work/projects/project-a?limit=invalid", nil)
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("got status %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})

	t.Run("invalid_offset", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/work/projects/project-a?offset=invalid", nil)
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("got status %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})

	t.Run("invalid_status_filter", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/work/projects/project-a?status=invalid", nil)
		rec := httptest.NewRecorder()

		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("got status %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})
}

func TestWorkHandler_Stats(t *testing.T) {
	mockQueue := newMockWorkQueue()
	workService := service.NewWorkService(mockQueue)
	handler := NewWorkHandler(workService)

	// Pre-populate tasks with various statuses
	mockQueue.tasks["task-1"] = &domain.WorkTask{ID: "task-1", Status: domain.WorkTaskStatusPending}
	mockQueue.tasks["task-2"] = &domain.WorkTask{ID: "task-2", Status: domain.WorkTaskStatusPending}
	mockQueue.tasks["task-3"] = &domain.WorkTask{ID: "task-3", Status: domain.WorkTaskStatusRunning}
	mockQueue.tasks["task-4"] = &domain.WorkTask{ID: "task-4", Status: domain.WorkTaskStatusCompleted}
	mockQueue.tasks["task-5"] = &domain.WorkTask{ID: "task-5", Status: domain.WorkTaskStatusFailed}

	router := chi.NewRouter()
	router.Use(testAdminAuth)
	handler.Mount(router)

	req := httptest.NewRequest(http.MethodGet, "/work/stats", nil)
	rec := httptest.NewRecorder()

	router.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Errorf("got status %d, want %d; body: %s", rec.Code, http.StatusOK, rec.Body.String())
	}

	var resp map[string]any
	if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil {
		t.Fatalf("failed to unmarshal: %v", err)
	}
	data := resp["data"].(map[string]any)

	if int(data["pending"].(float64)) != 2 {
		t.Errorf("got pending=%v, want 2", data["pending"])
	}
	if int(data["running"].(float64)) != 1 {
		t.Errorf("got running=%v, want 1", data["running"])
	}
	if int(data["completed"].(float64)) != 1 {
		t.Errorf("got completed=%v, want 1", data["completed"])
	}
	if int(data["failed"].(float64)) != 1 {
		t.Errorf("got failed=%v, want 1", data["failed"])
	}
}
