package docs

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"testing"

	"gitea.parodia.dev/drwily/petal/internal/db"
)

// newTestServer spins up an isolated on-disk database and the docs router.
func newTestServer(t *testing.T) http.Handler {
	t.Helper()
	database, err := db.Open(filepath.Join(t.TempDir(), "test.db"))
	if err != nil {
		t.Fatalf("open db: %v", err)
	}
	t.Cleanup(func() { database.Close() })
	return New(database).Routes()
}

func do(t *testing.T, srv http.Handler, method, path, body string) *httptest.ResponseRecorder {
	t.Helper()
	var r *http.Request
	if body != "" {
		r = httptest.NewRequest(method, path, bytes.NewBufferString(body))
	} else {
		r = httptest.NewRequest(method, path, nil)
	}
	rec := httptest.NewRecorder()
	srv.ServeHTTP(rec, r)
	return rec
}

func TestDocumentCRUD(t *testing.T) {
	srv := newTestServer(t)

	// Empty list serializes as [].
	rec := do(t, srv, http.MethodGet, "/", "")
	if rec.Code != http.StatusOK || bytes.TrimSpace(rec.Body.Bytes())[0] != '[' {
		t.Fatalf("list empty: code=%d body=%s", rec.Code, rec.Body)
	}

	// Create.
	rec = do(t, srv, http.MethodPost, "/", "")
	if rec.Code != http.StatusCreated {
		t.Fatalf("create: code=%d body=%s", rec.Code, rec.Body)
	}
	var created db.Document
	if err := json.Unmarshal(rec.Body.Bytes(), &created); err != nil {
		t.Fatalf("decode create: %v", err)
	}
	if created.ID == "" || created.Title != "Untitled" {
		t.Fatalf("unexpected created doc: %+v", created)
	}

	// Update (auto-save shape) keeps content + content_text + word count together.
	body := `{"title":"My Essay","content":"{\"x\":1}","content_text":"hello world","word_count":2}`
	rec = do(t, srv, http.MethodPut, "/"+created.ID, body)
	if rec.Code != http.StatusOK {
		t.Fatalf("update: code=%d body=%s", rec.Code, rec.Body)
	}
	var updated db.Document
	_ = json.Unmarshal(rec.Body.Bytes(), &updated)
	if updated.Title != "My Essay" || updated.ContentText != "hello world" || updated.WordCount != 2 {
		t.Fatalf("update not applied: %+v", updated)
	}

	// Partial update (rename only) leaves other fields intact.
	rec = do(t, srv, http.MethodPut, "/"+created.ID, `{"title":"Renamed"}`)
	_ = json.Unmarshal(rec.Body.Bytes(), &updated)
	if updated.Title != "Renamed" || updated.WordCount != 2 {
		t.Fatalf("partial update clobbered fields: %+v", updated)
	}

	// Get.
	rec = do(t, srv, http.MethodGet, "/"+created.ID, "")
	if rec.Code != http.StatusOK {
		t.Fatalf("get: code=%d", rec.Code)
	}

	// List now has one entry.
	rec = do(t, srv, http.MethodGet, "/", "")
	var summaries []docSummary
	_ = json.Unmarshal(rec.Body.Bytes(), &summaries)
	if len(summaries) != 1 || summaries[0].Title != "Renamed" {
		t.Fatalf("list after create: %+v", summaries)
	}

	// Delete, then 404 on subsequent get/delete.
	if rec = do(t, srv, http.MethodDelete, "/"+created.ID, ""); rec.Code != http.StatusNoContent {
		t.Fatalf("delete: code=%d", rec.Code)
	}
	if rec = do(t, srv, http.MethodGet, "/"+created.ID, ""); rec.Code != http.StatusNotFound {
		t.Fatalf("get after delete: code=%d", rec.Code)
	}
	if rec = do(t, srv, http.MethodPut, "/missing", `{"title":"x"}`); rec.Code != http.StatusNotFound {
		t.Fatalf("update missing: code=%d", rec.Code)
	}
}
