package worker

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

func TestNewAPIClient_DefaultTimeout(t *testing.T) {
	client := NewAPIClient(APIClientConfig{
		BaseURL: "http://localhost:8080",
		APIKey:  "test-key",
	})

	if client.httpClient.Timeout != 30*time.Second {
		t.Errorf("expected default timeout 30s, got %v", client.httpClient.Timeout)
	}
}

func TestNewAPIClient_CustomTimeout(t *testing.T) {
	client := NewAPIClient(APIClientConfig{
		BaseURL: "http://localhost:8080",
		APIKey:  "test-key",
		Timeout: 60 * time.Second,
	})

	if client.httpClient.Timeout != 60*time.Second {
		t.Errorf("expected timeout 60s, got %v", client.httpClient.Timeout)
	}
}

func TestRegister_Success(t *testing.T) {
	var receivedReq RegisterRequest
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("expected POST, got %s", r.Method)
		}
		if r.URL.Path != "/workers/register" {
			t.Errorf("expected /workers/register, got %s", r.URL.Path)
		}
		if r.Header.Get("Content-Type") != "application/json" {
			t.Errorf("expected Content-Type application/json, got %s", r.Header.Get("Content-Type"))
		}
		if r.Header.Get("X-API-Key") != "test-key" {
			t.Errorf("expected X-API-Key test-key, got %s", r.Header.Get("X-API-Key"))
		}

		body, _ := io.ReadAll(r.Body)
		_ = json.Unmarshal(body, &receivedReq)

		w.WriteHeader(http.StatusCreated)
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{
		BaseURL: server.URL,
		APIKey:  "test-key",
	})

	err := client.Register(context.Background(), &RegisterRequest{
		ID:           "worker-1",
		Hostname:     "localhost",
		Version:      "v1.0.0",
		Capabilities: []string{"build", "test"},
	})

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	if receivedReq.ID != "worker-1" {
		t.Errorf("expected ID worker-1, got %s", receivedReq.ID)
	}
	if receivedReq.Hostname != "localhost" {
		t.Errorf("expected hostname localhost, got %s", receivedReq.Hostname)
	}
}

func TestRegister_ErrorStatus(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusBadRequest)
		_, _ = w.Write([]byte(`{"error":"invalid worker ID"}`))
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	err := client.Register(context.Background(), &RegisterRequest{ID: "bad"})

	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if want := "register returned status 400"; !containsSubstring(err.Error(), want) {
		t.Errorf("expected error containing %q, got %v", want, err)
	}
	if !containsSubstring(err.Error(), "invalid worker ID") {
		t.Errorf("expected error to contain response body, got %v", err)
	}
}

func TestRegister_NetworkError(t *testing.T) {
	client := NewAPIClient(APIClientConfig{
		BaseURL: "http://localhost:1", // invalid port
		Timeout: 100 * time.Millisecond,
	})

	err := client.Register(context.Background(), &RegisterRequest{ID: "test"})

	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !containsSubstring(err.Error(), "register:") {
		t.Errorf("expected error wrapped with 'register:', got %v", err)
	}
}

func TestRegister_ContextCanceled(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		time.Sleep(1 * time.Second)
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	ctx, cancel := context.WithCancel(context.Background())
	cancel() // cancel immediately

	err := client.Register(ctx, &RegisterRequest{ID: "test"})

	if err == nil {
		t.Fatal("expected error, got nil")
	}
}

func TestHeartbeat_Success(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("expected POST, got %s", r.Method)
		}
		if r.URL.Path != "/workers/worker-123/heartbeat" {
			t.Errorf("expected /workers/worker-123/heartbeat, got %s", r.URL.Path)
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL, APIKey: "key"})

	err := client.Heartbeat(context.Background(), "worker-123")

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestHeartbeat_WorkerNotFound(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		_, _ = w.Write([]byte(`{"error":"worker not found"}`))
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	err := client.Heartbeat(context.Background(), "unknown-worker")

	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !containsSubstring(err.Error(), "heartbeat returned status 404") {
		t.Errorf("expected 404 error, got %v", err)
	}
}

func TestClaimTask_Success(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("expected POST, got %s", r.Method)
		}
		if r.URL.Path != "/workers/worker-1/claim" {
			t.Errorf("expected /workers/worker-1/claim, got %s", r.URL.Path)
		}

		resp := ClaimTaskResponse{
			Success: true,
			Data: struct {
				Task     *WorkTaskData `json:"task"`
				WorkerID string        `json:"worker_id"`
			}{
				WorkerID: "worker-1",
				Task: &WorkTaskData{
					ID:         "task-123",
					ProjectID:  "proj-1",
					Type:       "build",
					Status:     "running",
					Priority:   5,
					CreatedAt:  "2024-01-15T10:30:00Z",
					StartedAt:  "2024-01-15T10:31:00Z",
					RetryCount: 1,
					MaxRetries: 3,
					Spec:       map[string]any{"prompt": "build it"},
				},
			},
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(resp)
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	task, err := client.ClaimTask(context.Background(), "worker-1")

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if task == nil {
		t.Fatal("expected task, got nil")
	}
	if task.ID != "task-123" {
		t.Errorf("expected task ID task-123, got %s", task.ID)
	}
	if task.ProjectID != "proj-1" {
		t.Errorf("expected project ID proj-1, got %s", task.ProjectID)
	}
	if task.Type != domain.WorkTaskType("build") {
		t.Errorf("expected type build, got %s", task.Type)
	}
	if task.Priority != 5 {
		t.Errorf("expected priority 5, got %d", task.Priority)
	}
	if task.RetryCount != 1 {
		t.Errorf("expected retry count 1, got %d", task.RetryCount)
	}
}

func TestClaimTask_NoTasksAvailable(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	task, err := client.ClaimTask(context.Background(), "worker-1")

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if task != nil {
		t.Errorf("expected nil task, got %+v", task)
	}
}

func TestClaimTask_NilTaskData(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		resp := ClaimTaskResponse{
			Success: true,
			Data: struct {
				Task     *WorkTaskData `json:"task"`
				WorkerID string        `json:"worker_id"`
			}{
				WorkerID: "worker-1",
				Task:     nil,
			},
		}
		w.Header().Set("Content-Type", "application/json")
		_ = json.NewEncoder(w).Encode(resp)
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	_, err := client.ClaimTask(context.Background(), "worker-1")

	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !containsSubstring(err.Error(), "no task data") {
		t.Errorf("expected 'no task data' error, got %v", err)
	}
}

func TestClaimTask_MalformedJSON(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{invalid json}`))
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	_, err := client.ClaimTask(context.Background(), "worker-1")

	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !containsSubstring(err.Error(), "decode response") {
		t.Errorf("expected decode error, got %v", err)
	}
}

func TestClaimTask_ErrorStatus(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte(`{"error":"database error"}`))
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	_, err := client.ClaimTask(context.Background(), "worker-1")

	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !containsSubstring(err.Error(), "claim task returned status 500") {
		t.Errorf("expected 500 error, got %v", err)
	}
}

func TestCompleteTask_Success(t *testing.T) {
	var receivedReq CompleteTaskRequest
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("expected POST, got %s", r.Method)
		}
		if r.URL.Path != "/workers/worker-1/complete/task-123" {
			t.Errorf("expected /workers/worker-1/complete/task-123, got %s", r.URL.Path)
		}

		body, _ := io.ReadAll(r.Body)
		_ = json.Unmarshal(body, &receivedReq)

		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL, APIKey: "key"})

	result := &domain.BuildResult{
		Success:      true,
		Output:       "Build successful",
		CommitSHA:    "abc123",
		FilesChanged: []string{"main.go", "go.mod"},
		DurationMs:   5000,
		Artifacts:    map[string]string{"deploy_url": "https://example.com"},
	}

	err := client.CompleteTask(context.Background(), "worker-1", "task-123", result)

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !receivedReq.Success {
		t.Error("expected success=true")
	}
	if receivedReq.Output != "Build successful" {
		t.Errorf("expected output 'Build successful', got %s", receivedReq.Output)
	}
	if receivedReq.CommitSHA != "abc123" {
		t.Errorf("expected commit SHA abc123, got %s", receivedReq.CommitSHA)
	}
	if receivedReq.DurationMs != 5000 {
		t.Errorf("expected duration 5000, got %d", receivedReq.DurationMs)
	}
}

func TestCompleteTask_TaskNotFound(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNotFound)
		_, _ = w.Write([]byte(`{"error":"task not found"}`))
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	err := client.CompleteTask(context.Background(), "worker-1", "unknown", &domain.BuildResult{})

	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !containsSubstring(err.Error(), "complete task returned status 404") {
		t.Errorf("expected 404 error, got %v", err)
	}
}

func TestFailTask_Success(t *testing.T) {
	var receivedReq FailTaskRequest
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			t.Errorf("expected POST, got %s", r.Method)
		}
		if r.URL.Path != "/workers/worker-1/fail/task-123" {
			t.Errorf("expected /workers/worker-1/fail/task-123, got %s", r.URL.Path)
		}

		body, _ := io.ReadAll(r.Body)
		_ = json.Unmarshal(body, &receivedReq)

		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL, APIKey: "key"})

	err := client.FailTask(context.Background(), "worker-1", "task-123", "build failed", "error output", 3000)

	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if receivedReq.Error != "build failed" {
		t.Errorf("expected error 'build failed', got %s", receivedReq.Error)
	}
	if receivedReq.Output != "error output" {
		t.Errorf("expected output 'error output', got %s", receivedReq.Output)
	}
	if receivedReq.DurationMs != 3000 {
		t.Errorf("expected duration 3000, got %d", receivedReq.DurationMs)
	}
}

func TestFailTask_ErrorStatus(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
		_, _ = w.Write([]byte(`{"error":"database unavailable"}`))
	}))
	defer server.Close()

	client := NewAPIClient(APIClientConfig{BaseURL: server.URL})

	err := client.FailTask(context.Background(), "worker-1", "task-123", "failed", "", 0)

	if err == nil {
		t.Fatal("expected error, got nil")
	}
	if !containsSubstring(err.Error(), "fail task returned status 500") {
		t.Errorf("expected 500 error, got %v", err)
	}
}

func TestToWorkTask_ParsesTimestamps(t *testing.T) {
	data := &WorkTaskData{
		ID:        "task-1",
		ProjectID: "proj-1",
		Type:      "build",
		Status:    "running",
		CreatedAt: "2024-01-15T10:30:00Z",
		StartedAt: "2024-01-15T10:31:00Z",
	}

	task := data.ToWorkTask()

	if task.CreatedAt.IsZero() {
		t.Error("expected CreatedAt to be set")
	}
	if task.CreatedAt.Year() != 2024 || task.CreatedAt.Month() != 1 || task.CreatedAt.Day() != 15 {
		t.Errorf("unexpected CreatedAt: %v", task.CreatedAt)
	}

	if task.StartedAt == nil {
		t.Fatal("expected StartedAt to be set")
	}
	if task.StartedAt.Hour() != 10 || task.StartedAt.Minute() != 31 {
		t.Errorf("unexpected StartedAt: %v", task.StartedAt)
	}
}

func TestToWorkTask_InvalidTimestamp(t *testing.T) {
	data := &WorkTaskData{
		ID:        "task-1",
		ProjectID: "proj-1",
		Type:      "build",
		Status:    "running",
		CreatedAt: "not-a-timestamp",
		StartedAt: "also-not-valid",
	}

	task := data.ToWorkTask()

	// Invalid timestamps should result in zero values (current behavior)
	if !task.CreatedAt.IsZero() {
		t.Errorf("expected zero CreatedAt for invalid timestamp, got %v", task.CreatedAt)
	}
	// Note: StartedAt is parsed but ignored on error, pointer becomes non-nil with zero value
}

func TestToWorkTask_EmptyTimestamps(t *testing.T) {
	data := &WorkTaskData{
		ID:        "task-1",
		ProjectID: "proj-1",
		Type:      "build",
		Status:    "pending",
		CreatedAt: "",
		StartedAt: "",
	}

	task := data.ToWorkTask()

	if !task.CreatedAt.IsZero() {
		t.Errorf("expected zero CreatedAt for empty string, got %v", task.CreatedAt)
	}
	if task.StartedAt != nil {
		t.Errorf("expected nil StartedAt for empty string, got %v", task.StartedAt)
	}
}

func TestToWorkTask_NilData(t *testing.T) {
	var data *WorkTaskData
	task := data.ToWorkTask()

	if task != nil {
		t.Errorf("expected nil task from nil data, got %+v", task)
	}
}

func TestToWorkTask_PreservesAllFields(t *testing.T) {
	data := &WorkTaskData{
		ID:          "task-1",
		ProjectID:   "proj-1",
		Type:        "deploy",
		Status:      "completed",
		Priority:    10,
		WorkerID:    "worker-99",
		CallbackURL: "https://callback.example.com",
		RetryCount:  2,
		MaxRetries:  5,
		Spec:        map[string]any{"key": "value"},
	}

	task := data.ToWorkTask()

	if task.ID != "task-1" {
		t.Errorf("expected ID task-1, got %s", task.ID)
	}
	if task.ProjectID != "proj-1" {
		t.Errorf("expected ProjectID proj-1, got %s", task.ProjectID)
	}
	if task.Type != domain.WorkTaskType("deploy") {
		t.Errorf("expected Type deploy, got %s", task.Type)
	}
	if task.Status != domain.WorkTaskStatus("completed") {
		t.Errorf("expected Status completed, got %s", task.Status)
	}
	if task.Priority != 10 {
		t.Errorf("expected Priority 10, got %d", task.Priority)
	}
	if task.WorkerID != "worker-99" {
		t.Errorf("expected WorkerID worker-99, got %s", task.WorkerID)
	}
	if task.CallbackURL != "https://callback.example.com" {
		t.Errorf("expected CallbackURL, got %s", task.CallbackURL)
	}
	if task.RetryCount != 2 {
		t.Errorf("expected RetryCount 2, got %d", task.RetryCount)
	}
	if task.MaxRetries != 5 {
		t.Errorf("expected MaxRetries 5, got %d", task.MaxRetries)
	}
	if task.Spec["key"] != "value" {
		t.Errorf("expected Spec[key]=value, got %v", task.Spec)
	}
}

// containsSubstring is a helper to check if a string contains a substring.
func containsSubstring(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > 0 && containsHelper(s, substr))
}

func containsHelper(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
