package rest_test

import (
	"net/http"
	"strings"
	"testing"

	"github.com/pingcap/agent-git-service/internal/db"
	"github.com/pingcap/agent-git-service/internal/testharness"
)

func TestUpdateAuthenticatedUserProfile(t *testing.T) {
	h := testharness.New(t)

	updated := h.DoRESTJSON(t, http.MethodPatch, "/api/v3/user", map[string]any{
		"name":     "  Hazel  ",
		"bio":      "  Building AGS.  ",
		"company":  "  ClawMem  ",
		"blog":     "  https://clawmem.ai  ",
		"location": "  Shanghai  ",
	})
	assertStatusCode(t, updated, http.StatusOK)
	body := testharness.DecodeJSON(t, updated)
	assertUserProfileField(t, body, "name", "Hazel")
	assertUserProfileField(t, body, "bio", "Building AGS.")
	assertUserProfileField(t, body, "company", "ClawMem")
	assertUserProfileField(t, body, "blog", "https://clawmem.ai")
	assertUserProfileField(t, body, "location", "Shanghai")

	var persisted db.User
	if err := h.DB.First(&persisted, h.User.ID).Error; err != nil {
		t.Fatalf("reload updated user: %v", err)
	}
	if !persisted.NameSetByUser {
		t.Fatal("NameSetByUser = false, want true after PATCH name")
	}
	if persisted.Name != "Hazel" || persisted.Company != "ClawMem" || persisted.Blog != "https://clawmem.ai" || persisted.Location != "Shanghai" {
		t.Fatalf("persisted profile = %#v", persisted)
	}

	partial := h.DoRESTJSON(t, http.MethodPatch, "/api/v3/user", map[string]any{"bio": ""})
	assertStatusCode(t, partial, http.StatusOK)
	partialBody := testharness.DecodeJSON(t, partial)
	assertUserProfileField(t, partialBody, "bio", "")
	assertUserProfileField(t, partialBody, "company", "ClawMem")

	public := h.DoREST(t, http.MethodGet, "/api/v3/users/testuser", nil)
	assertStatusCode(t, public, http.StatusOK)
	publicBody := testharness.DecodeJSON(t, public)
	assertUserProfileField(t, publicBody, "company", "ClawMem")
	assertUserProfileField(t, publicBody, "blog", "https://clawmem.ai")
	assertUserProfileField(t, publicBody, "location", "Shanghai")
	assertUserProfileField(t, publicBody, "email", "")
}

func TestUpdateAuthenticatedUserProfileRejectsInvalidOrIdentityManagedFields(t *testing.T) {
	h := testharness.New(t)

	tests := []struct {
		name string
		body string
		want int
	}{
		{name: "unknown field", body: `{"login":"other"}`, want: http.StatusBadRequest},
		{name: "trailing JSON", body: `{"name":"Hazel"} {}`, want: http.StatusBadRequest},
		{name: "malformed JSON", body: `{"name":`, want: http.StatusBadRequest},
		{name: "identity managed email", body: `{"email":"other@example.com"}`, want: http.StatusUnprocessableEntity},
		{name: "oversized name", body: `{"name":"` + strings.Repeat("x", 256) + `"}`, want: http.StatusUnprocessableEntity},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			w := h.DoREST(t, http.MethodPatch, "/api/v3/user", strings.NewReader(tt.body))
			assertStatusCode(t, w, tt.want)
		})
	}

	unchangedEmail := h.DoRESTJSON(t, http.MethodPatch, "/api/v3/user", map[string]any{"email": h.User.Email, "name": "Hazel"})
	assertStatusCode(t, unchangedEmail, http.StatusOK)

	unauthenticated := h.DoRESTNoAuth(t, http.MethodPatch, "/api/v3/user")
	assertStatusCode(t, unauthenticated, http.StatusUnauthorized)
}

func TestUpdateAuthenticatedUserProfileOpenAPIContract(t *testing.T) {
	h := testharness.New(t)
	w := h.DoREST(t, http.MethodGet, "/api/v3/openapi.json", nil)
	assertStatusCode(t, w, http.StatusOK)

	document := testharness.DecodeJSON(t, w)
	paths, ok := document["paths"].(map[string]any)
	if !ok {
		t.Fatalf("OpenAPI paths = %#v", document["paths"])
	}
	currentUserPath, ok := paths["/api/v3/user"].(map[string]any)
	if !ok {
		t.Fatalf("OpenAPI current-user path = %#v", paths["/api/v3/user"])
	}
	patch, ok := currentUserPath["patch"].(map[string]any)
	if !ok || patch["operationId"] != "updateAuthenticatedUser" {
		t.Fatalf("OpenAPI PATCH /api/v3/user = %#v", currentUserPath["patch"])
	}
}

func assertUserProfileField(t *testing.T, body map[string]any, name, want string) {
	t.Helper()
	if got, ok := body[name].(string); !ok || got != want {
		t.Fatalf("%s = %#v, want %q", name, body[name], want)
	}
}
