// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

package lunatask_test

import (
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"
	"time"

	lunatask "git.secluded.site/go-lunatask"
)

const (
	sourceGitHub = "github"
	sourceID123  = "123"
	taskID       = "066b5835-184f-4fd9-be60-7d735aa94708"
	areaID       = "11b37775-5a34-41bb-b109-f0e5a6084799"
)

// --- ListTasks ---

func TestListTasks_Success(t *testing.T) {
	t.Parallel()

	server := newJSONServer(t, "/tasks", tasksResponseBody)
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))

	tasks, err := client.ListTasks(ctx(), nil)
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	if len(tasks) != 2 {
		t.Fatalf("len = %d, want 2", len(tasks))
	}

	task := tasks[0]
	if task.ID != taskID {
		t.Errorf("ID = %q, want %q", task.ID, taskID)
	}

	if task.AreaID == nil || *task.AreaID != areaID {
		t.Errorf("AreaID = %v, want %q", task.AreaID, areaID)
	}

	if task.Status == nil || *task.Status != lunatask.StatusNext {
		t.Errorf("Status = %v, want %v", task.Status, lunatask.StatusNext)
	}

	wantCreated := time.Date(2021, 1, 10, 10, 39, 25, 0, time.UTC)
	if !task.CreatedAt.Equal(wantCreated) {
		t.Errorf("CreatedAt = %v, want %v", task.CreatedAt, wantCreated)
	}
}

func TestListTasks_Filter(t *testing.T) {
	t.Parallel()

	tests := []filterTest{
		{
			Name:      "source_only",
			Source:    ptr(sourceGitHub),
			SourceID:  nil,
			WantQuery: url.Values{"source": {sourceGitHub}},
		},
		{
			Name:      "source_and_id",
			Source:    ptr(sourceGitHub),
			SourceID:  ptr(sourceID123),
			WantQuery: url.Values{"source": {sourceGitHub}, "source_id": {sourceID123}},
		},
	}

	runFilterTests(t, "/tasks", `{"tasks": []}`, tests, func(c *lunatask.Client, source, sourceID *string) error {
		opts := &lunatask.ListTasksOptions{Source: source, SourceID: sourceID}
		_, err := c.ListTasks(ctx(), opts)

		return err //nolint:wrapcheck // test helper
	})
}

func TestListTasks_Errors(t *testing.T) {
	t.Parallel()

	testErrorCases(t, func(c *lunatask.Client) error {
		_, err := c.ListTasks(ctx(), nil)

		return err //nolint:wrapcheck // test helper
	})
}

func TestListTasks_Empty(t *testing.T) {
	t.Parallel()

	server := newJSONServer(t, "/tasks", `{"tasks": []}`)
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))

	tasks, err := client.ListTasks(ctx(), nil)
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	if len(tasks) != 0 {
		t.Errorf("len = %d, want 0", len(tasks))
	}
}

// --- GetTask ---

func TestGetTask_Success(t *testing.T) {
	t.Parallel()

	server := newJSONServer(t, "/tasks/"+taskID, singleTaskResponseBody)
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))

	task, err := client.GetTask(ctx(), taskID)
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	if task == nil {
		t.Fatal("returned nil")
	}

	if task.ID != taskID {
		t.Errorf("ID = %q, want %q", task.ID, taskID)
	}

	if task.AreaID == nil || *task.AreaID != areaID {
		t.Errorf("AreaID = %v, want %q", task.AreaID, areaID)
	}

	if len(task.Sources) != 1 || task.Sources[0].Source != sourceGitHub {
		t.Errorf("Sources = %v, want github source", task.Sources)
	}
}

func TestGetTask_Errors(t *testing.T) {
	t.Parallel()

	testErrorCases(t, func(c *lunatask.Client) error {
		_, err := c.GetTask(ctx(), "some-id")

		return err //nolint:wrapcheck // test helper
	})
}

// --- CreateTask ---

func TestCreateTask_Success(t *testing.T) {
	t.Parallel()

	server, capture := newPOSTServer(t, "/tasks", singleTaskResponseBody)
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))

	task, err := client.NewTask("My task").
		InArea(areaID).
		FromSource(sourceGitHub, sourceID123).
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	if task == nil {
		t.Fatal("returned nil")
	}

	if task.ID != taskID {
		t.Errorf("ID = %q, want %q", task.ID, taskID)
	}

	assertBodyField(t, capture.Body, "name", "My task")
	assertBodyField(t, capture.Body, "area_id", areaID)
	assertBodyField(t, capture.Body, "source", sourceGitHub)
	assertBodyField(t, capture.Body, "source_id", sourceID123)
}

func TestCreateTask_AllBuilderFields(t *testing.T) {
	t.Parallel()

	server, capture := newPOSTServer(t, "/tasks", singleTaskResponseBody)
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
	goalID := "goal-uuid-here"
	scheduledDate := lunatask.NewDate(time.Date(2024, 3, 15, 0, 0, 0, 0, time.UTC))
	completedTime := time.Date(2024, 3, 15, 14, 30, 0, 0, time.UTC)

	_, err := client.NewTask("Full task").
		InArea(areaID).
		InGoal(goalID).
		WithNote("Some markdown note").
		WithStatus(lunatask.StatusNext).
		WithMotivation(lunatask.MotivationWant).
		WithEisenhower(1).
		WithEstimate(60).
		Priority(lunatask.PriorityHighest).
		ScheduledOn(scheduledDate).
		CompletedAt(completedTime).
		FromSource(sourceGitHub, sourceID123).
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	assertBodyField(t, capture.Body, "name", "Full task")
	assertBodyField(t, capture.Body, "area_id", areaID)
	assertBodyField(t, capture.Body, "goal_id", goalID)
	assertBodyField(t, capture.Body, "note", "Some markdown note")
	assertBodyField(t, capture.Body, "status", "next")
	assertBodyField(t, capture.Body, "motivation", "want")
	assertBodyField(t, capture.Body, "scheduled_on", "2024-03-15")
	assertBodyField(t, capture.Body, "completed_at", "2024-03-15T14:30:00Z")
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 1)
	assertBodyFieldFloat(t, capture.Body, "estimate", 60)
	assertBodyFieldFloat(t, capture.Body, "priority", 2)
}

func TestCreateTask_Duplicate(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, _ *http.Request) {
		writer.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))

	task, err := client.NewTask("Duplicate task").
		InArea(areaID).
		FromSource(sourceGitHub, sourceID123).
		Create(ctx())
		// Per AGENTS.md: Create methods return (nil, nil) for duplicates
	if err != nil {
		t.Fatalf("error = %v, want nil", err)
	}

	if task != nil {
		t.Errorf("task = %v, want nil for duplicate", task)
	}
}

func TestCreateTask_Errors(t *testing.T) {
	t.Parallel()

	testErrorCases(t, func(c *lunatask.Client) error {
		_, err := c.NewTask("Test").InArea(areaID).Create(ctx())

		return err //nolint:wrapcheck // test helper
	})
}

// --- UpdateTask ---

func TestUpdateTask_Success(t *testing.T) {
	t.Parallel()

	server, capture := newPUTServer(t, "/tasks/"+taskID, singleTaskResponseBody)
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))

	task, err := client.NewTaskUpdate(taskID).
		Name("Updated name").
		WithStatus(lunatask.StatusCompleted).
		WithMotivation(lunatask.MotivationMust).
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	if task == nil {
		t.Fatal("returned nil")
	}

	if task.ID != taskID {
		t.Errorf("ID = %q, want %q", task.ID, taskID)
	}

	assertBodyField(t, capture.Body, "name", "Updated name")
	assertBodyField(t, capture.Body, "status", "completed")
	assertBodyField(t, capture.Body, "motivation", "must")
}

func TestUpdateTask_AllBuilderFields(t *testing.T) {
	t.Parallel()

	server, capture := newPUTServer(t, "/tasks/"+taskID, singleTaskResponseBody)
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
	scheduledDate := lunatask.NewDate(time.Date(2024, 6, 20, 0, 0, 0, 0, time.UTC))
	completedTime := time.Date(2024, 6, 20, 16, 45, 0, 0, time.UTC)

	_, err := client.NewTaskUpdate(taskID).
		Name("Full update").
		InArea(areaID).
		InGoal("goal-id").
		WithNote("Updated note").
		WithStatus(lunatask.StatusInProgress).
		WithMotivation(lunatask.MotivationShould).
		WithEisenhower(2).
		WithEstimate(90).
		Priority(lunatask.PriorityLow).
		ScheduledOn(scheduledDate).
		CompletedAt(completedTime).
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	assertBodyField(t, capture.Body, "name", "Full update")
	assertBodyField(t, capture.Body, "area_id", areaID)
	assertBodyField(t, capture.Body, "goal_id", "goal-id")
	assertBodyField(t, capture.Body, "note", "Updated note")
	assertBodyField(t, capture.Body, "status", "started")
	assertBodyField(t, capture.Body, "motivation", "should")
	assertBodyField(t, capture.Body, "scheduled_on", "2024-06-20")
	assertBodyField(t, capture.Body, "completed_at", "2024-06-20T16:45:00Z")
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 2)
	assertBodyFieldFloat(t, capture.Body, "estimate", 90)
	assertBodyFieldFloat(t, capture.Body, "priority", -1)
}

func TestUpdateTask_Errors(t *testing.T) {
	t.Parallel()

	testErrorCases(t, func(c *lunatask.Client) error {
		_, err := c.NewTaskUpdate(taskID).Name("x").Update(ctx())

		return err //nolint:wrapcheck // test helper
	})
}

// --- DeleteTask ---

func TestDeleteTask_Success(t *testing.T) {
	t.Parallel()

	server := newDELETEServer(t, "/tasks/"+taskID, singleTaskResponseBody)
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))

	task, err := client.DeleteTask(ctx(), taskID)
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	if task == nil {
		t.Fatal("returned nil")
	}

	if task.ID != taskID {
		t.Errorf("ID = %q, want %q", task.ID, taskID)
	}
}

func TestDeleteTask_Errors(t *testing.T) {
	t.Parallel()

	testErrorCases(t, func(c *lunatask.Client) error {
		_, err := c.DeleteTask(ctx(), taskID)

		return err //nolint:wrapcheck // test helper
	})
}

// --- Test Data ---

const singleTaskResponseBody = `{
	"task": {
		"id": "066b5835-184f-4fd9-be60-7d735aa94708",
		"area_id": "11b37775-5a34-41bb-b109-f0e5a6084799",
		"goal_id": null,
		"status": "next",
		"previous_status": "later",
		"estimate": 10,
		"priority": 0,
		"progress": null,
		"motivation": "unknown",
		"eisenhower": 0,
		"sources": [{"source": "github", "source_id": "123"}],
		"scheduled_on": null,
		"completed_at": null,
		"created_at": "2021-01-10T10:39:25Z",
		"updated_at": "2021-01-10T10:39:25Z"
	}
}`

const tasksResponseBody = `{
	"tasks": [
		{
			"id": "066b5835-184f-4fd9-be60-7d735aa94708",
			"area_id": "11b37775-5a34-41bb-b109-f0e5a6084799",
			"goal_id": null,
			"status": "next",
			"previous_status": "later",
			"estimate": 10,
			"priority": 0,
			"progress": 25,
			"motivation": "unknown",
			"eisenhower": 0,
			"sources": [{"source": "github", "source_id": "123"}],
			"scheduled_on": null,
			"completed_at": null,
			"created_at": "2021-01-10T10:39:25Z",
			"updated_at": "2021-01-10T10:39:25Z"
		},
		{
			"id": "0e0cff5c-c334-4a24-b15a-4fca6cfbf25f",
			"area_id": "f557287e-ae43-4472-9478-497887362dcb",
			"goal_id": null,
			"status": "later",
			"previous_status": null,
			"estimate": 120,
			"priority": 0,
			"motivation": "unknown",
			"eisenhower": 0,
			"progress": null,
			"sources": [],
			"scheduled_on": null,
			"completed_at": null,
			"created_at": "2021-01-10T10:39:26Z",
			"updated_at": "2021-01-10T10:39:26Z"
		}
	]
}`
