package service

import (
	"context"
	"testing"
	"time"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
)

func TestWorkerService_Register(t *testing.T) {
	ctx := context.Background()

	t.Run("registers valid worker", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		queue := newMockWorkQueue()
		svc := NewWorkerService(registry, queue)

		err := svc.Register(ctx, &domain.Worker{
			ID:           "worker-1",
			Hostname:     "host-1",
			Capabilities: []string{"build"},
			Version:      "1.0.0",
		})
		if err != nil {
			t.Fatalf("Register() error = %v", err)
		}

		w := registry.workers["worker-1"]
		if w == nil {
			t.Fatal("worker not found in registry")
		}
		if w.Status != domain.WorkerStatusIdle {
			t.Errorf("got status %q, want %q", w.Status, domain.WorkerStatusIdle)
		}
		if w.RegisteredAt.IsZero() {
			t.Error("expected registered_at to be set")
		}
	})

	t.Run("validates worker", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		queue := newMockWorkQueue()
		svc := NewWorkerService(registry, queue)

		err := svc.Register(ctx, &domain.Worker{})
		if err == nil {
			t.Error("expected validation error for empty worker")
		}
	})
}

func TestWorkerService_Heartbeat(t *testing.T) {
	ctx := context.Background()

	t.Run("updates heartbeat", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		registry.workers["worker-1"] = &domain.Worker{
			ID:            "worker-1",
			Hostname:      "host-1",
			Status:        domain.WorkerStatusIdle,
			LastHeartbeat: time.Now().Add(-30 * time.Second),
		}

		svc := NewWorkerService(registry, newMockWorkQueue())

		err := svc.Heartbeat(ctx, "worker-1")
		if err != nil {
			t.Fatalf("Heartbeat() error = %v", err)
		}

		// Heartbeat should be recent
		w := registry.workers["worker-1"]
		if time.Since(w.LastHeartbeat) > time.Second {
			t.Error("expected heartbeat to be updated to now")
		}
	})

	t.Run("returns error for nonexistent worker", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		svc := NewWorkerService(registry, newMockWorkQueue())

		err := svc.Heartbeat(ctx, "nonexistent")
		if err == nil {
			t.Error("expected error for nonexistent worker")
		}
	})
}

func TestWorkerService_Deregister(t *testing.T) {
	ctx := context.Background()

	t.Run("deregisters worker", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		registry.workers["worker-1"] = &domain.Worker{
			ID:       "worker-1",
			Hostname: "host-1",
			Status:   domain.WorkerStatusIdle,
		}

		svc := NewWorkerService(registry, newMockWorkQueue())

		err := svc.Deregister(ctx, "worker-1")
		if err != nil {
			t.Fatalf("Deregister() error = %v", err)
		}

		if _, ok := registry.workers["worker-1"]; ok {
			t.Error("worker should be removed from registry")
		}
	})
}

func TestWorkerService_ClaimTask(t *testing.T) {
	ctx := context.Background()

	t.Run("claims task and marks worker busy", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		registry.workers["worker-1"] = &domain.Worker{
			ID:       "worker-1",
			Hostname: "host-1",
			Status:   domain.WorkerStatusIdle,
		}

		queue := newMockWorkQueue()
		queue.tasks["task-1"] = &domain.WorkTask{
			ID:        "task-1",
			ProjectID: "project-1",
			Type:      domain.WorkTaskTypeBuild,
			Status:    domain.WorkTaskStatusPending,
			CreatedAt: time.Now(),
		}

		svc := NewWorkerService(registry, queue)

		task, err := svc.ClaimTask(ctx, "worker-1")
		if err != nil {
			t.Fatalf("ClaimTask() error = %v", err)
		}
		if task == nil {
			t.Fatal("expected task to be returned")
		}
		if task.ID != "task-1" {
			t.Errorf("got task ID %q, want %q", task.ID, "task-1")
		}

		// Worker should be busy with the task
		w := registry.workers["worker-1"]
		if w.Status != domain.WorkerStatusBusy {
			t.Errorf("got status %q, want %q", w.Status, domain.WorkerStatusBusy)
		}
		if w.CurrentTask != "task-1" {
			t.Errorf("got current_task %q, want %q", w.CurrentTask, "task-1")
		}
	})

	t.Run("returns nil when no tasks available", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		registry.workers["worker-1"] = &domain.Worker{
			ID:       "worker-1",
			Hostname: "host-1",
			Status:   domain.WorkerStatusIdle,
		}

		queue := newMockWorkQueue()
		svc := NewWorkerService(registry, queue)

		task, err := svc.ClaimTask(ctx, "worker-1")
		if err != nil {
			t.Fatalf("ClaimTask() error = %v", err)
		}
		if task != nil {
			t.Error("expected nil task when queue is empty")
		}
	})

	t.Run("updates audit status when claiming task", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		registry.workers["worker-1"] = &domain.Worker{
			ID:       "worker-1",
			Hostname: "host-1",
			Status:   domain.WorkerStatusIdle,
		}

		queue := newMockWorkQueue()
		queue.tasks["task-1"] = &domain.WorkTask{
			ID:        "task-1",
			ProjectID: "project-1",
			Type:      domain.WorkTaskTypeBuild,
			Status:    domain.WorkTaskStatusPending,
			CreatedAt: time.Now(),
		}

		audit := newMockBuildAudit()
		audit.entries["task-1"] = &domain.BuildAuditEntry{
			TaskID:    "task-1",
			ProjectID: "project-1",
			Status:    domain.BuildStatusPending,
		}

		svc := NewWorkerService(registry, queue).WithBuildAudit(audit)

		task, err := svc.ClaimTask(ctx, "worker-1")
		if err != nil {
			t.Fatalf("ClaimTask() error = %v", err)
		}
		if task == nil {
			t.Fatal("expected task to be returned")
		}

		// Verify audit was updated
		entry := audit.entries["task-1"]
		if entry.Status != domain.BuildStatusRunning {
			t.Errorf("got audit status %q, want %q", entry.Status, domain.BuildStatusRunning)
		}
		if entry.WorkerID != "worker-1" {
			t.Errorf("got audit worker_id %q, want %q", entry.WorkerID, "worker-1")
		}
	})
}

func TestWorkerService_CompleteTask(t *testing.T) {
	ctx := context.Background()

	t.Run("completes task and returns worker to idle", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		registry.workers["worker-1"] = &domain.Worker{
			ID:          "worker-1",
			Hostname:    "host-1",
			Status:      domain.WorkerStatusBusy,
			CurrentTask: "task-1",
		}

		queue := newMockWorkQueue()
		queue.tasks["task-1"] = &domain.WorkTask{
			ID:        "task-1",
			ProjectID: "project-1",
			Type:      domain.WorkTaskTypeBuild,
			Status:    domain.WorkTaskStatusRunning,
			WorkerID:  "worker-1",
		}

		svc := NewWorkerService(registry, queue)

		err := svc.CompleteTask(ctx, "worker-1", "task-1", &domain.BuildResult{
			Success:    true,
			CommitSHA:  "abc123",
			DurationMs: 5000,
		})
		if err != nil {
			t.Fatalf("CompleteTask() error = %v", err)
		}

		// Task should be completed
		task := queue.tasks["task-1"]
		if task.Status != domain.WorkTaskStatusCompleted {
			t.Errorf("got task status %q, want %q", task.Status, domain.WorkTaskStatusCompleted)
		}

		// Worker should be idle
		w := registry.workers["worker-1"]
		if w.Status != domain.WorkerStatusIdle {
			t.Errorf("got worker status %q, want %q", w.Status, domain.WorkerStatusIdle)
		}
		if w.CurrentTask != "" {
			t.Errorf("got current_task %q, want empty", w.CurrentTask)
		}
	})

	t.Run("handles nil result", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		registry.workers["worker-1"] = &domain.Worker{
			ID:          "worker-1",
			Hostname:    "host-1",
			Status:      domain.WorkerStatusBusy,
			CurrentTask: "task-1",
		}

		queue := newMockWorkQueue()
		queue.tasks["task-1"] = &domain.WorkTask{
			ID:       "task-1",
			Status:   domain.WorkTaskStatusRunning,
			WorkerID: "worker-1",
		}

		svc := NewWorkerService(registry, queue)

		err := svc.CompleteTask(ctx, "worker-1", "task-1", nil)
		if err != nil {
			t.Fatalf("CompleteTask(nil result) error = %v", err)
		}

		// Worker should be idle
		w := registry.workers["worker-1"]
		if w.Status != domain.WorkerStatusIdle {
			t.Errorf("got worker status %q, want %q", w.Status, domain.WorkerStatusIdle)
		}
	})
}

func TestWorkerService_ListWorkers(t *testing.T) {
	ctx := context.Background()

	registry := newMockWorkerRegistry()
	registry.workers["worker-1"] = &domain.Worker{ID: "worker-1", Status: domain.WorkerStatusIdle}
	registry.workers["worker-2"] = &domain.Worker{ID: "worker-2", Status: domain.WorkerStatusBusy}
	registry.workers["worker-3"] = &domain.Worker{ID: "worker-3", Status: domain.WorkerStatusIdle}

	svc := NewWorkerService(registry, newMockWorkQueue())

	t.Run("lists all workers", func(t *testing.T) {
		workers, err := svc.ListWorkers(ctx, port.WorkerFilter{})
		if err != nil {
			t.Fatalf("ListWorkers() error = %v", err)
		}
		if len(workers) != 3 {
			t.Errorf("got %d workers, want 3", len(workers))
		}
	})

	t.Run("filters by status", func(t *testing.T) {
		idle := domain.WorkerStatusIdle
		workers, err := svc.ListWorkers(ctx, port.WorkerFilter{Status: &idle})
		if err != nil {
			t.Fatalf("ListWorkers() error = %v", err)
		}
		if len(workers) != 2 {
			t.Errorf("got %d idle workers, want 2", len(workers))
		}
	})
}

func TestWorkerService_DrainWorker(t *testing.T) {
	ctx := context.Background()

	t.Run("drains worker", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		registry.workers["worker-1"] = &domain.Worker{
			ID:          "worker-1",
			Hostname:    "host-1",
			Status:      domain.WorkerStatusBusy,
			CurrentTask: "task-1",
		}

		svc := NewWorkerService(registry, newMockWorkQueue())

		err := svc.DrainWorker(ctx, "worker-1")
		if err != nil {
			t.Fatalf("DrainWorker() error = %v", err)
		}

		w := registry.workers["worker-1"]
		if w.Status != domain.WorkerStatusDraining {
			t.Errorf("got status %q, want %q", w.Status, domain.WorkerStatusDraining)
		}
		// Should preserve current task
		if w.CurrentTask != "task-1" {
			t.Errorf("got current_task %q, want %q", w.CurrentTask, "task-1")
		}
	})

	t.Run("returns error for nonexistent worker", func(t *testing.T) {
		registry := newMockWorkerRegistry()
		svc := NewWorkerService(registry, newMockWorkQueue())

		err := svc.DrainWorker(ctx, "nonexistent")
		if err == nil {
			t.Error("expected error for nonexistent worker")
		}
	})
}
