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

package lunatask_test

import (
	"errors"
	"testing"

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

// --- Eisenhower String Method ---

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

	tests := []struct {
		name  string
		value lunatask.Eisenhower
		want  string
	}{
		{"uncategorized", lunatask.EisenhowerUncategorized, "uncategorized"},
		{"do_now", lunatask.EisenhowerDoNow, "do now (important + urgent)"},
		{"delegate", lunatask.EisenhowerDelegate, "delegate (urgent)"},
		{"do_later", lunatask.EisenhowerDoLater, "do later (important)"},
		{"eliminate", lunatask.EisenhowerEliminate, "eliminate"},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			if got := tc.value.String(); got != tc.want {
				t.Errorf("Eisenhower(%d).String() = %q, want %q", tc.value, got, tc.want)
			}
		})
	}
}

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

	unknown := lunatask.Eisenhower(99)
	want := "Eisenhower(99)"

	if got := unknown.String(); got != want {
		t.Errorf("Eisenhower(99).String() = %q, want %q", got, want)
	}
}

// --- Eisenhower Type Constants ---

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

	tests := []struct {
		name  string
		value lunatask.Eisenhower
		want  int
	}{
		{"uncategorized", lunatask.EisenhowerUncategorized, 0},
		{"do_now", lunatask.EisenhowerDoNow, 1},
		{"delegate", lunatask.EisenhowerDelegate, 2},
		{"do_later", lunatask.EisenhowerDoLater, 3},
		{"eliminate", lunatask.EisenhowerEliminate, 4},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			if int(tc.value) != tc.want {
				t.Errorf("Eisenhower constant = %d, want %d", tc.value, tc.want)
			}
		})
	}
}

// --- Eisenhower Helper Methods ---

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

	tests := []struct {
		name  string
		value lunatask.Eisenhower
		want  bool
	}{
		{"uncategorized", lunatask.EisenhowerUncategorized, false},
		{"do_now", lunatask.EisenhowerDoNow, true},         // urgent + important
		{"delegate", lunatask.EisenhowerDelegate, true},    // urgent only
		{"do_later", lunatask.EisenhowerDoLater, false},    // important only
		{"eliminate", lunatask.EisenhowerEliminate, false}, // neither
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			if got := tc.value.IsUrgent(); got != tc.want {
				t.Errorf("Eisenhower(%d).IsUrgent() = %v, want %v", tc.value, got, tc.want)
			}
		})
	}
}

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

	tests := []struct {
		name  string
		value lunatask.Eisenhower
		want  bool
	}{
		{"uncategorized", lunatask.EisenhowerUncategorized, false},
		{"do_now", lunatask.EisenhowerDoNow, true},         // urgent + important
		{"delegate", lunatask.EisenhowerDelegate, false},   // urgent only
		{"do_later", lunatask.EisenhowerDoLater, true},     // important only
		{"eliminate", lunatask.EisenhowerEliminate, false}, // neither
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()

			if got := tc.value.IsImportant(); got != tc.want {
				t.Errorf("Eisenhower(%d).IsImportant() = %v, want %v", tc.value, got, tc.want)
			}
		})
	}
}

// --- Eisenhower Helper Function ---

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

	tests := []struct {
		name      string
		important bool
		urgent    bool
		want      lunatask.Eisenhower
	}{
		{"both", true, true, lunatask.EisenhowerDoNow},
		{"urgent_only", false, true, lunatask.EisenhowerDelegate},
		{"important_only", true, false, lunatask.EisenhowerDoLater},
		{"neither", false, false, lunatask.EisenhowerEliminate},
	}

	for _, testCase := range tests {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()

			got := lunatask.NewEisenhower(testCase.important, testCase.urgent)
			if got != testCase.want {
				t.Errorf("NewEisenhower(%v, %v) = %d, want %d", testCase.important, testCase.urgent, got, testCase.want)
			}
		})
	}
}

// --- TaskBuilder Semantic Methods ---

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

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

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

	_, err := client.NewTask("Important task").
		Important().
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	// Important only = 3 (DoLater)
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 3)
}

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

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

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

	_, err := client.NewTask("Urgent task").
		Urgent().
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	// Urgent only = 2 (Delegate)
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 2)
}

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

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

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

	_, err := client.NewTask("Do now task").
		Important().
		Urgent().
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	// Both = 1 (DoNow)
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 1)
}

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

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

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

	_, err := client.NewTask("Do now task").
		Urgent().
		Important().
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	// Both = 1 (DoNow), order shouldn't matter
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 1)
}

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

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

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

	// Explicitly set neither - should result in Eliminate (4)
	_, err := client.NewTask("Eliminate task").
		NotImportant().
		NotUrgent().
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	// Neither = 4 (Eliminate)
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 4)
}

// --- TaskUpdateBuilder Semantic Methods ---

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

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

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

	_, err := client.NewTaskUpdate(taskID).
		Important().
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	// Important only = 3 (DoLater)
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 3)
}

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

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

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

	_, err := client.NewTaskUpdate(taskID).
		Urgent().
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	// Urgent only = 2 (Delegate)
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 2)
}

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

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

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

	_, err := client.NewTaskUpdate(taskID).
		Important().
		Urgent().
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	// Both = 1 (DoNow)
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 1)
}

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

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

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

	_, err := client.NewTaskUpdate(taskID).
		NotImportant().
		NotUrgent().
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	// Neither = 4 (Eliminate)
	assertBodyFieldFloat(t, capture.Body, "eisenhower", 4)
}

// --- WithEisenhower still works with typed constant ---

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

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

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

	_, err := client.NewTask("Typed eisenhower").
		WithEisenhower(lunatask.EisenhowerDoNow).
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	assertBodyFieldFloat(t, capture.Body, "eisenhower", 1)
}

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

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

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

	_, err := client.NewTaskUpdate(taskID).
		WithEisenhower(lunatask.EisenhowerDelegate).
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	assertBodyFieldFloat(t, capture.Body, "eisenhower", 2)
}

// --- ParseEisenhower ---

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

	tests := []struct {
		name    string
		input   string
		want    lunatask.Eisenhower
		wantErr bool
	}{
		// Text values
		{"uncategorized_lower", "uncategorized", lunatask.EisenhowerUncategorized, false},
		{"uncategorized_upper", "UNCATEGORIZED", lunatask.EisenhowerUncategorized, false},
		{"do_now_lower", "do-now", lunatask.EisenhowerDoNow, false},
		{"do_now_upper", "DO-NOW", lunatask.EisenhowerDoNow, false},
		{"donow_lower", "donow", lunatask.EisenhowerDoNow, false},
		{"donow_upper", "DONOW", lunatask.EisenhowerDoNow, false},
		{"delegate_lower", "delegate", lunatask.EisenhowerDelegate, false},
		{"delegate_upper", "DELEGATE", lunatask.EisenhowerDelegate, false},
		{"do_later_lower", "do-later", lunatask.EisenhowerDoLater, false},
		{"do_later_upper", "DO-LATER", lunatask.EisenhowerDoLater, false},
		{"dolater_lower", "dolater", lunatask.EisenhowerDoLater, false},
		{"dolater_upper", "DOLATER", lunatask.EisenhowerDoLater, false},
		{"eliminate_lower", "eliminate", lunatask.EisenhowerEliminate, false},
		{"eliminate_upper", "ELIMINATE", lunatask.EisenhowerEliminate, false},

		// Numeric values
		{"zero", "0", lunatask.EisenhowerUncategorized, false},
		{"one", "1", lunatask.EisenhowerDoNow, false},
		{"two", "2", lunatask.EisenhowerDelegate, false},
		{"three", "3", lunatask.EisenhowerDoLater, false},
		{"four", "4", lunatask.EisenhowerEliminate, false},

		// Invalid values
		{"invalid", "invalid", 0, true},
		{"empty", "", 0, true},
		{"five", "5", 0, true},
		{"negative", "-1", 0, true},
		{"typo", "elimate", 0, true},
	}

	for _, testCase := range tests {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()

			got, err := lunatask.ParseEisenhower(testCase.input)
			if (err != nil) != testCase.wantErr {
				t.Errorf("ParseEisenhower(%q) error = %v, wantErr %v", testCase.input, err, testCase.wantErr)

				return
			}

			if testCase.wantErr {
				if !errors.Is(err, lunatask.ErrInvalidEisenhower) {
					t.Errorf("ParseEisenhower(%q) error = %v, want ErrInvalidEisenhower", testCase.input, err)
				}

				return
			}

			if got != testCase.want {
				t.Errorf("ParseEisenhower(%q) = %v, want %v", testCase.input, got, testCase.want)
			}
		})
	}
}
