package handlers

import (
	"context"
	"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/port"
	"github.com/orchard9/rdev/internal/service"
)

// mockWorkerRegistry implements port.WorkerRegistry for testing.
type mockWorkerRegistry struct {
	workers map[string]*domain.Worker
	err     error
}

func newMockWorkerRegistry() *mockWorkerRegistry {
	return &mockWorkerRegistry{
		workers: make(map[string]*domain.Worker),
	}
}

func (m *mockWorkerRegistry) Register(_ context.Context, w *domain.Worker) error {
	if m.err != nil {
		return m.err
	}
	m.workers[w.ID] = w
	return nil
}

func (m *mockWorkerRegistry) Heartbeat(_ context.Context, workerID string) error {
	if m.err != nil {
		return m.err
	}
	w, ok := m.workers[workerID]
	if !ok {
		return domain.ErrWorkerNotFound
	}
	w.LastHeartbeat = time.Now()
	return nil
}

func (m *mockWorkerRegistry) UpdateStatus(_ context.Context, workerID string, status domain.WorkerStatus, taskID string) error {
	if m.err != nil {
		return m.err
	}
	w, ok := m.workers[workerID]
	if !ok {
		return domain.ErrWorkerNotFound
	}
	w.Status = status
	w.CurrentTask = taskID
	return nil
}

func (m *mockWorkerRegistry) Deregister(_ context.Context, workerID string) error {
	if m.err != nil {
		return m.err
	}
	delete(m.workers, workerID)
	return nil
}

func (m *mockWorkerRegistry) Get(_ context.Context, workerID string) (*domain.Worker, error) {
	if m.err != nil {
		return nil, m.err
	}
	w, ok := m.workers[workerID]
	if !ok {
		return nil, domain.ErrWorkerNotFound
	}
	return w, nil
}

func (m *mockWorkerRegistry) List(_ context.Context, filter port.WorkerFilter) ([]*domain.Worker, error) {
	if m.err != nil {
		return nil, m.err
	}
	var result []*domain.Worker
	for _, w := range m.workers {
		if filter.Status != nil && w.Status != *filter.Status {
			continue
		}
		result = append(result, w)
	}
	return result, nil
}

func (m *mockWorkerRegistry) MarkStaleOffline(_ context.Context, _ time.Duration) (int, error) {
	return 0, m.err
}

func TestWorkersHandler_List(t *testing.T) {
	registry := newMockWorkerRegistry()
	queue := newMockWorkQueue()
	workerService := service.NewWorkerService(registry, queue)
	handler := NewWorkersHandler(workerService)

	// Populate workers
	registry.workers["worker-1"] = &domain.Worker{
		ID:            "worker-1",
		Hostname:      "host-1",
		Status:        domain.WorkerStatusIdle,
		Capabilities:  []string{"build"},
		RegisteredAt:  time.Now(),
		LastHeartbeat: time.Now(),
		Version:       "1.0.0",
	}
	registry.workers["worker-2"] = &domain.Worker{
		ID:            "worker-2",
		Hostname:      "host-2",
		Status:        domain.WorkerStatusBusy,
		CurrentTask:   "task-abc",
		Capabilities:  []string{"build", "deploy"},
		RegisteredAt:  time.Now(),
		LastHeartbeat: time.Now(),
		Version:       "1.0.0",
	}

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

	t.Run("list_all_workers", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/workers", 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, ok := resp["data"].(map[string]any)
		if !ok {
			t.Fatalf("expected data to be map, got %T", resp["data"])
		}
		totalF, ok := data["total"].(float64)
		if !ok {
			t.Fatalf("expected total to be float64, got %T", data["total"])
		}
		if int(totalF) != 2 {
			t.Errorf("got total=%d, want 2", int(totalF))
		}

		summary, ok := data["summary"].(map[string]any)
		if !ok {
			t.Fatalf("expected summary to be map, got %T", data["summary"])
		}
		if idleF, ok := summary["idle"].(float64); !ok || int(idleF) != 1 {
			t.Errorf("got idle=%v, want 1", summary["idle"])
		}
		if busyF, ok := summary["busy"].(float64); !ok || int(busyF) != 1 {
			t.Errorf("got busy=%v, want 1", summary["busy"])
		}
	})

	t.Run("filter_by_status", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/workers?status=idle", 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, ok := resp["data"].(map[string]any)
		if !ok {
			t.Fatalf("expected data to be map, got %T", resp["data"])
		}
		totalF, ok := data["total"].(float64)
		if !ok {
			t.Fatalf("expected total to be float64, got %T", data["total"])
		}
		if int(totalF) != 1 {
			t.Errorf("got total=%d, want 1", int(totalF))
		}
	})

	t.Run("invalid_status_filter", func(t *testing.T) {
		req := httptest.NewRequest(http.MethodGet, "/workers?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 TestWorkersHandler_Get(t *testing.T) {
	registry := newMockWorkerRegistry()
	queue := newMockWorkQueue()
	workerService := service.NewWorkerService(registry, queue)
	handler := NewWorkersHandler(workerService)

	registry.workers["worker-1"] = &domain.Worker{
		ID:            "worker-1",
		Hostname:      "host-1",
		Status:        domain.WorkerStatusIdle,
		RegisteredAt:  time.Now(),
		LastHeartbeat: time.Now(),
	}

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

	tests := []struct {
		name       string
		workerID   string
		wantStatus int
	}{
		{
			name:       "existing_worker",
			workerID:   "worker-1",
			wantStatus: http.StatusOK,
		},
		{
			name:       "not_found",
			workerID:   "nonexistent",
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodGet, "/workers/"+tt.workerID, 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 TestWorkersHandler_Drain(t *testing.T) {
	registry := newMockWorkerRegistry()
	queue := newMockWorkQueue()
	workerService := service.NewWorkerService(registry, queue)
	handler := NewWorkersHandler(workerService)

	registry.workers["worker-1"] = &domain.Worker{
		ID:            "worker-1",
		Hostname:      "host-1",
		Status:        domain.WorkerStatusBusy,
		CurrentTask:   "task-abc",
		RegisteredAt:  time.Now(),
		LastHeartbeat: time.Now(),
	}

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

	tests := []struct {
		name       string
		workerID   string
		wantStatus int
	}{
		{
			name:       "drain_existing_worker",
			workerID:   "worker-1",
			wantStatus: http.StatusOK,
		},
		{
			name:       "drain_nonexistent_worker",
			workerID:   "nonexistent",
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(http.MethodPost, "/workers/"+tt.workerID+"/drain", 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())
			}
		})
	}

	// Verify the worker was actually set to draining
	if registry.workers["worker-1"].Status != domain.WorkerStatusDraining {
		t.Errorf("expected worker status to be draining, got %s", registry.workers["worker-1"].Status)
	}
}
