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

package lunatask_test

import (
	"encoding/json"
	"testing"

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

// --- AllPriorities Function ---

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

	priorities := lunatask.AllPriorities()

	// Check count
	if got := len(priorities); got != 5 {
		t.Fatalf("AllPriorities() returned %d values, want 5", got)
	}

	// Check order (lowest to highest)
	expected := []lunatask.Priority{
		lunatask.PriorityLowest,
		lunatask.PriorityLow,
		lunatask.PriorityNormal,
		lunatask.PriorityHigh,
		lunatask.PriorityHighest,
	}
	for i, want := range expected {
		if priorities[i] != want {
			t.Errorf("AllPriorities()[%d] = %d, want %d", i, priorities[i], want)
		}
	}

	// Check roundtrip: each value should be parseable
	for _, priority := range priorities {
		parsed, err := lunatask.ParsePriority(priority.String())
		if err != nil {
			t.Errorf("ParsePriority(%q) failed: %v", priority.String(), err)
		}

		if parsed != priority {
			t.Errorf("ParsePriority(%q) = %d, want %d", priority.String(), parsed, priority)
		}
	}
}

// --- Priority Type Constants ---

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

	tests := []struct {
		name  string
		value lunatask.Priority
		want  int
	}{
		{"lowest", lunatask.PriorityLowest, -2},
		{"low", lunatask.PriorityLow, -1},
		{"normal", lunatask.PriorityNormal, 0},
		{"high", lunatask.PriorityHigh, 1},
		{"highest", lunatask.PriorityHighest, 2},
	}

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

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

// --- Priority String Method ---

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

	tests := []struct {
		name  string
		value lunatask.Priority
		want  string
	}{
		{"lowest", lunatask.PriorityLowest, "lowest"},
		{"low", lunatask.PriorityLow, "low"},
		{"normal", lunatask.PriorityNormal, "normal"},
		{"high", lunatask.PriorityHigh, "high"},
		{"highest", lunatask.PriorityHighest, "highest"},
	}

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

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

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

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

// --- Priority Valid Method ---

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

	tests := []struct {
		name  string
		value lunatask.Priority
		want  bool
	}{
		{"lowest", lunatask.PriorityLowest, true},
		{"low", lunatask.PriorityLow, true},
		{"normal", lunatask.PriorityNormal, true},
		{"high", lunatask.PriorityHigh, true},
		{"highest", lunatask.PriorityHighest, true},
		{"below_range", lunatask.Priority(-3), false},
		{"above_range", lunatask.Priority(3), false},
		{"way_above", lunatask.Priority(99), false},
	}

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

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

// --- ParsePriority Function ---

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

	tests := []struct {
		name    string
		input   string
		want    lunatask.Priority
		wantErr bool
	}{
		{"lowest_lower", "lowest", lunatask.PriorityLowest, false},
		{"lowest_upper", "LOWEST", lunatask.PriorityLowest, false},
		{"lowest_mixed", "LoWeSt", lunatask.PriorityLowest, false},
		{"low_lower", "low", lunatask.PriorityLow, false},
		{"low_upper", "LOW", lunatask.PriorityLow, false},
		{"normal_lower", "normal", lunatask.PriorityNormal, false},
		{"normal_upper", "NORMAL", lunatask.PriorityNormal, false},
		{"high_lower", "high", lunatask.PriorityHigh, false},
		{"high_upper", "HIGH", lunatask.PriorityHigh, false},
		{"highest_lower", "highest", lunatask.PriorityHighest, false},
		{"highest_upper", "HIGHEST", lunatask.PriorityHighest, false},
		{"invalid", "invalid", 0, true},
		{"empty", "", 0, true},
		{"numeric", "1", 0, true},
	}

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

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

				return
			}

			if !testCase.wantErr && got != testCase.want {
				t.Errorf("ParsePriority(%q) = %d, want %d", testCase.input, got, testCase.want)
			}
		})
	}
}

// --- Priority JSON Marshaling ---

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

	tests := []struct {
		name  string
		value lunatask.Priority
		want  string
	}{
		{"lowest", lunatask.PriorityLowest, "-2"},
		{"low", lunatask.PriorityLow, "-1"},
		{"normal", lunatask.PriorityNormal, "0"},
		{"high", lunatask.PriorityHigh, "1"},
		{"highest", lunatask.PriorityHighest, "2"},
	}

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

			got, err := json.Marshal(testCase.value)
			if err != nil {
				t.Fatalf("Marshal error: %v", err)
			}

			if string(got) != testCase.want {
				t.Errorf("json.Marshal(Priority) = %s, want %s", got, testCase.want)
			}
		})
	}
}

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

	tests := []struct {
		name    string
		input   string
		want    lunatask.Priority
		wantErr bool
	}{
		{"lowest", "-2", lunatask.PriorityLowest, false},
		{"low", "-1", lunatask.PriorityLow, false},
		{"normal", "0", lunatask.PriorityNormal, false},
		{"high", "1", lunatask.PriorityHigh, false},
		{"highest", "2", lunatask.PriorityHighest, false},
		{"null", "null", lunatask.PriorityNormal, false},
		{"out_of_range_high", "5", 0, true},
		{"out_of_range_low", "-5", 0, true},
		{"string", `"high"`, 0, true},
	}

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

			var got lunatask.Priority

			err := json.Unmarshal([]byte(testCase.input), &got)

			if (err != nil) != testCase.wantErr {
				t.Errorf("Unmarshal error = %v, wantErr %v", err, testCase.wantErr)

				return
			}

			if !testCase.wantErr && got != testCase.want {
				t.Errorf("Unmarshal(%s) = %d, want %d", testCase.input, got, testCase.want)
			}
		})
	}
}

// --- TaskBuilder Priority Method ---

func TestTaskBuilder_Priority(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("High priority task").
		Priority(lunatask.PriorityHigh).
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

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

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

	tests := []struct {
		name     string
		priority lunatask.Priority
		want     float64
	}{
		{"lowest", lunatask.PriorityLowest, -2},
		{"low", lunatask.PriorityLow, -1},
		{"normal", lunatask.PriorityNormal, 0},
		{"high", lunatask.PriorityHigh, 1},
		{"highest", lunatask.PriorityHighest, 2},
	}

	for _, testCase := range tests {
		t.Run(testCase.name, func(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("Task").
				Priority(testCase.priority).
				Create(ctx())
			if err != nil {
				t.Fatalf("error = %v", err)
			}

			assertBodyFieldFloat(t, capture.Body, "priority", testCase.want)
		})
	}
}

// --- TaskUpdateBuilder Priority Method ---

func TestTaskUpdateBuilder_Priority(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).
		Priority(lunatask.PriorityLowest).
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	assertBodyFieldFloat(t, capture.Body, "priority", -2)
}

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

	tests := []struct {
		name     string
		priority lunatask.Priority
		want     float64
	}{
		{"lowest", lunatask.PriorityLowest, -2},
		{"low", lunatask.PriorityLow, -1},
		{"normal", lunatask.PriorityNormal, 0},
		{"high", lunatask.PriorityHigh, 1},
		{"highest", lunatask.PriorityHighest, 2},
	}

	for _, testCase := range tests {
		t.Run(testCase.name, func(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).
				Priority(testCase.priority).
				Update(ctx())
			if err != nil {
				t.Fatalf("error = %v", err)
			}

			assertBodyFieldFloat(t, capture.Body, "priority", testCase.want)
		})
	}
}
