package httpserver

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

	"github.com/tim/cairnquire/apps/server/internal/auth"
	"github.com/tim/cairnquire/apps/server/internal/collaboration"
)

func loginTestUser(t *testing.T, server *apiTestServer) *http.Cookie {
	t.Helper()
	login := httptest.NewRecorder()
	server.handler.ServeHTTP(login, jsonRequest(http.MethodPost, "/api/auth/login/password", map[string]string{
		"email": "editor@example.com", "password": "very secure password",
	}))
	if login.Code != http.StatusOK {
		t.Fatalf("login response = %d %q", login.Code, login.Body.String())
	}
	return cookieByName(t, login.Result().Cookies(), auth.SessionCookieName)
}

func authenticatedJSONRequest(t *testing.T, method, target string, body any, session *http.Cookie) *http.Request {
	t.Helper()
	request := jsonRequest(method, target, body)
	request.AddCookie(session)
	return request
}

func TestDocumentLoadsCollaborationControlsAndWatchFlow(t *testing.T) {
	server := newAPITestServer(t)
	session := loginTestUser(t, server)

	pageRequest := httptest.NewRequest(http.MethodGet, "/docs/hello", nil)
	pageRequest.AddCookie(session)
	page := httptest.NewRecorder()
	server.handler.ServeHTTP(page, pageRequest)
	if page.Code != http.StatusOK {
		t.Fatalf("document response = %d %q", page.Code, page.Body.String())
	}
	for _, expected := range [][]byte{
		[]byte(`/static/notification-bell.js`),
		[]byte(`data-document-id="doc:hello"`),
		[]byte(`data-document-watch`),
		[]byte(`data-comments-history`),
	} {
		if !bytes.Contains(page.Body.Bytes(), expected) {
			t.Errorf("document page missing %q", expected)
		}
	}

	statusRequest := httptest.NewRequest(http.MethodGet, "/api/watch/status?document_id=doc%3Ahello", nil)
	statusRequest.AddCookie(session)
	status := httptest.NewRecorder()
	server.handler.ServeHTTP(status, statusRequest)
	if status.Code != http.StatusOK || !bytes.Contains(status.Body.Bytes(), []byte(`"watching":false`)) {
		t.Fatalf("initial watch status = %d %q", status.Code, status.Body.String())
	}

	watch := httptest.NewRecorder()
	server.handler.ServeHTTP(watch, authenticatedJSONRequest(t, http.MethodPost, "/api/watch/document", map[string]string{"documentId": "doc:hello"}, session))
	if watch.Code != http.StatusOK {
		t.Fatalf("watch response = %d %q", watch.Code, watch.Body.String())
	}

	status = httptest.NewRecorder()
	server.handler.ServeHTTP(status, statusRequest.Clone(statusRequest.Context()))
	if status.Code != http.StatusOK || !bytes.Contains(status.Body.Bytes(), []byte(`"watching":true`)) {
		t.Fatalf("watched status = %d %q", status.Code, status.Body.String())
	}

	unwatch := httptest.NewRecorder()
	server.handler.ServeHTTP(unwatch, authenticatedJSONRequest(t, http.MethodDelete, "/api/watch/document", map[string]string{"documentId": "doc:hello"}, session))
	if unwatch.Code != http.StatusOK {
		t.Fatalf("unwatch response = %d %q", unwatch.Code, unwatch.Body.String())
	}
}

func TestCommentReplyResolveAndHistoryHTTPFlow(t *testing.T) {
	server := newAPITestServer(t)
	session := loginTestUser(t, server)
	anchor := "section-anchor"

	create := func(content string, parentID *string) collaboration.Comment {
		t.Helper()
		response := httptest.NewRecorder()
		server.handler.ServeHTTP(response, authenticatedJSONRequest(t, http.MethodPost, "/api/comments", collaboration.CreateCommentInput{
			DocumentID: "doc:hello", VersionHash: "hash", ParentID: parentID, Content: content, AnchorHash: &anchor,
		}, session))
		if response.Code != http.StatusCreated {
			t.Fatalf("create comment response = %d %q", response.Code, response.Body.String())
		}
		var comment collaboration.Comment
		if err := json.NewDecoder(response.Body).Decode(&comment); err != nil {
			t.Fatalf("decode comment: %v", err)
		}
		return comment
	}

	root := create("Root", nil)
	reply := create("Reply", &root.ID)
	_ = create("Grandchild", &reply.ID)

	resolve := httptest.NewRecorder()
	server.handler.ServeHTTP(resolve, authenticatedJSONRequest(t, http.MethodPost, "/api/comments/"+root.ID+"/resolve", map[string]any{}, session))
	if resolve.Code != http.StatusOK {
		t.Fatalf("resolve response = %d %q", resolve.Code, resolve.Body.String())
	}

	list := func(includeResolved bool) []collaboration.Comment {
		t.Helper()
		target := "/api/comments?document_id=doc%3Ahello&anchor_hash=" + anchor
		if includeResolved {
			target += "&include_resolved=true"
		}
		request := httptest.NewRequest(http.MethodGet, target, nil)
		request.AddCookie(session)
		response := httptest.NewRecorder()
		server.handler.ServeHTTP(response, request)
		if response.Code != http.StatusOK {
			t.Fatalf("list comments response = %d %q", response.Code, response.Body.String())
		}
		var body struct {
			Comments []collaboration.Comment `json:"comments"`
		}
		if err := json.NewDecoder(response.Body).Decode(&body); err != nil {
			t.Fatalf("decode comments: %v", err)
		}
		return body.Comments
	}

	if active := list(false); len(active) != 0 {
		t.Fatalf("active comments = %d, want 0", len(active))
	}
	if history := list(true); len(history) != 3 {
		t.Fatalf("history comments = %d, want 3", len(history))
	}
}
