// 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 (
	noteID     = "5999b945-b2b1-48c6-aa72-b251b75b3c2e"
	notebookID = "d1ff35f5-6b25-4199-ab6e-c19fe3fe27f1"
)

// --- ListNotes ---

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

	server := newJSONServer(t, "/notes", notesResponseBody)
	defer server.Close()

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

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

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

	note := notes[0]
	if note.ID != noteID {
		t.Errorf("ID = %q, want %q", note.ID, noteID)
	}

	if note.NotebookID == nil || *note.NotebookID != notebookID {
		t.Errorf("NotebookID = %v, want %q", note.NotebookID, notebookID)
	}

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

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

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

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

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

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

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

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

// --- GetNote (undocumented but supported) ---

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

	server := newJSONServer(t, "/notes/"+noteID, singleNoteResponseBody)
	defer server.Close()

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

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

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

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

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

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

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

// --- CreateNote ---

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

	server, capture := newPOSTServer(t, "/notes", singleNoteResponseBody)
	defer server.Close()

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

	note, err := client.NewNote().
		WithName("My note").
		WithContent("Note content").
		InNotebook(notebookID).
		FromSource("evernote", "ext-123").
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

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

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

	assertBodyField(t, capture.Body, "name", "My note")
	assertBodyField(t, capture.Body, "content", "Note content")
	assertBodyField(t, capture.Body, "notebook_id", notebookID)
	assertBodyField(t, capture.Body, "source", "evernote")
	assertBodyField(t, capture.Body, "source_id", "ext-123")
}

func TestCreateNote_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))

	note, err := client.NewNote().
		WithName("Duplicate").
		InNotebook(notebookID).
		FromSource("evernote", "dup-123").
		Create(ctx())
	if err != nil {
		t.Fatalf("error = %v, want nil", err)
	}

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

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

	testErrorCases(t, func(c *lunatask.Client) error {
		_, err := c.NewNote().WithName("x").Create(ctx())

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

// --- UpdateNote ---

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

	server, capture := newPUTServer(t, "/notes/"+noteID, singleNoteResponseBody)
	defer server.Close()

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

	note, err := client.NewNoteUpdate(noteID).
		WithName("Updated name").
		WithContent("New content").
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

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

	assertBodyField(t, capture.Body, "name", "Updated name")
	assertBodyField(t, capture.Body, "content", "New content")
}

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

	server, capture := newPUTServer(t, "/notes/"+noteID, singleNoteResponseBody)
	defer server.Close()

	client := lunatask.NewClient(testToken, lunatask.BaseURL(server.URL))
	dateOn := lunatask.NewDate(time.Date(2024, 5, 10, 0, 0, 0, 0, time.UTC))

	_, err := client.NewNoteUpdate(noteID).
		WithName("Full update").
		WithContent("Full content").
		InNotebook("new-notebook-id").
		OnDate(dateOn).
		Update(ctx())
	if err != nil {
		t.Fatalf("error = %v", err)
	}

	assertBodyField(t, capture.Body, "name", "Full update")
	assertBodyField(t, capture.Body, "content", "Full content")
	assertBodyField(t, capture.Body, "notebook_id", "new-notebook-id")
	assertBodyField(t, capture.Body, "date_on", "2024-05-10")
}

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

	testErrorCases(t, func(c *lunatask.Client) error {
		_, err := c.NewNoteUpdate(noteID).WithName("x").Update(ctx())

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

// --- DeleteNote ---

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

	server := newDELETEServer(t, "/notes/"+noteID, singleNoteResponseBody)
	defer server.Close()

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

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

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

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

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

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

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

// --- Test Data ---

const singleNoteResponseBody = `{
	"note": {
		"id": "5999b945-b2b1-48c6-aa72-b251b75b3c2e",
		"notebook_id": "d1ff35f5-6b25-4199-ab6e-c19fe3fe27f1",
		"date_on": null,
		"pinned": false,
		"sources": [{"source": "evernote", "source_id": "352fd2d7-cdc0-4e91-a0a3-9d6cc9d440e7"}],
		"created_at": "2021-01-10T10:39:25Z",
		"updated_at": "2021-01-10T10:39:25Z"
	}
}`

const notesResponseBody = `{
	"notes": [
		{
			"id": "5999b945-b2b1-48c6-aa72-b251b75b3c2e",
			"notebook_id": "d1ff35f5-6b25-4199-ab6e-c19fe3fe27f1",
			"date_on": null,
			"pinned": false,
			"sources": [{"source": "evernote", "source_id": "352fd2d7-cdc0-4e91-a0a3-9d6cc9d440e7"}],
			"created_at": "2021-01-10T10:39:25Z",
			"updated_at": "2021-01-10T10:39:25Z"
		},
		{
			"id": "2ca8eb4c-4825-47e4-84de-2bbe0017b6c0",
			"notebook_id": "fc2aa380-3320-4525-8611-7332d5060478",
			"date_on": null,
			"pinned": false,
			"sources": [],
			"created_at": "2021-01-13T08:12:25Z",
			"updated_at": "2021-01-15T10:39:25Z"
		}
	]
}`
