package rest_test

import (
	"bytes"
	"image"
	"image/color"
	"image/png"
	"mime/multipart"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"testing"

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

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

	upload := doAvatarUpload(t, h, "/api/ext/v1/user/avatar", h.Token, avatarTestPNG(t), "avatar.png")
	assertStatusCode(t, upload, http.StatusOK)
	updated := testharness.DecodeJSON(t, upload)
	avatarURL, _ := updated["avatar_url"].(string)
	if !strings.HasSuffix(avatarURL, "/avatars/testuser?v=1") {
		t.Fatalf("avatar_url = %q, want versioned local URL", avatarURL)
	}

	path := avatarRequestPath(t, avatarURL)
	served := httptest.NewRecorder()
	h.Mux.ServeHTTP(served, httptest.NewRequest(http.MethodGet, path, nil))
	assertStatusCode(t, served, http.StatusOK)
	if got := served.Header().Get("Content-Type"); got != "image/png" {
		t.Fatalf("Content-Type = %q, want image/png", got)
	}
	if got := served.Header().Get("Cache-Control"); got != "public, max-age=31536000, immutable" {
		t.Fatalf("Cache-Control = %q", got)
	}
	if served.Header().Get("Location") != "" {
		t.Fatalf("avatar response must not redirect externally: %q", served.Header().Get("Location"))
	}

	deleted := h.DoREST(t, http.MethodDelete, "/api/ext/v1/user/avatar", nil)
	assertStatusCode(t, deleted, http.StatusOK)
	cleared := testharness.DecodeJSON(t, deleted)
	clearedURL, _ := cleared["avatar_url"].(string)
	if !strings.HasSuffix(clearedURL, "/avatars/testuser?v=2") {
		t.Fatalf("cleared avatar_url = %q, want new generated-avatar version", clearedURL)
	}

	stale := httptest.NewRecorder()
	h.Mux.ServeHTTP(stale, httptest.NewRequest(http.MethodGet, path, nil))
	assertStatusCode(t, stale, http.StatusNotFound)

	generated := httptest.NewRecorder()
	h.Mux.ServeHTTP(generated, httptest.NewRequest(http.MethodGet, avatarRequestPath(t, clearedURL), nil))
	assertStatusCode(t, generated, http.StatusOK)
	if generated.Body.Len() == 0 {
		t.Fatal("generated fallback body is empty")
	}
}

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

	malformed := h.DoREST(t, http.MethodPut, "/api/ext/v1/user/avatar", strings.NewReader("not multipart"))
	assertStatusCode(t, malformed, http.StatusBadRequest)

	invalid := doAvatarUpload(t, h, "/api/ext/v1/user/avatar", h.Token, []byte("not an image"), "avatar.txt")
	assertStatusCode(t, invalid, http.StatusUnsupportedMediaType)

	tooLarge := doAvatarUpload(t, h, "/api/ext/v1/user/avatar", h.Token, make([]byte, service.AvatarMaxUploadBytes+1), "large.png")
	assertStatusCode(t, tooLarge, http.StatusRequestEntityTooLarge)

	overRequestLimit := doAvatarUpload(t, h, "/api/ext/v1/user/avatar", h.Token, make([]byte, service.AvatarMaxUploadBytes+256*1024), "larger.png")
	assertStatusCode(t, overRequestLimit, http.StatusRequestEntityTooLarge)
}

func TestAvatarHandlers_OrganizationOwnerOnly(t *testing.T) {
	h := testharness.New(t)
	ctx := h.Tenant.Context()
	owner, ownerToken := seedHarnessUser(t, h, "avatar-org-owner", false)
	member, memberToken := seedHarnessUser(t, h, "avatar-org-member", false)
	org, err := h.Svc.EnsureOrg(testharness.ContextWithUser(t, ctx, owner), "avatar-org")
	if err != nil {
		t.Fatalf("EnsureOrg: %v", err)
	}
	if err := h.Svc.AddOrgMember(ctx, org.ID, member.ID, db.OrganizationRoleMember); err != nil {
		t.Fatalf("AddOrgMember: %v", err)
	}

	denied := doAvatarUpload(t, h, "/api/ext/v1/orgs/avatar-org/avatar", memberToken, avatarTestPNG(t), "avatar.png")
	assertStatusCode(t, denied, http.StatusForbidden)

	allowed := doAvatarUpload(t, h, "/api/ext/v1/orgs/avatar-org/avatar", ownerToken, avatarTestPNG(t), "avatar.png")
	assertStatusCode(t, allowed, http.StatusOK)
	updated := testharness.DecodeJSON(t, allowed)
	if url, _ := updated["avatar_url"].(string); !strings.HasSuffix(url, "/avatars/avatar-org?v=1") {
		t.Fatalf("organization avatar_url = %q", url)
	}
}

func doAvatarUpload(t *testing.T, h *testharness.Harness, path, token string, content []byte, name string) *httptest.ResponseRecorder {
	t.Helper()
	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	part, err := writer.CreateFormFile("file", name)
	if err != nil {
		t.Fatalf("CreateFormFile: %v", err)
	}
	if _, err := part.Write(content); err != nil {
		t.Fatalf("write file part: %v", err)
	}
	if err := writer.Close(); err != nil {
		t.Fatalf("close multipart: %v", err)
	}
	req := httptest.NewRequest(http.MethodPut, path, &body)
	req.Header.Set("Authorization", "token "+token)
	req.Header.Set("Content-Type", writer.FormDataContentType())
	response := httptest.NewRecorder()
	h.Mux.ServeHTTP(response, req)
	return response
}

func avatarRequestPath(t *testing.T, raw string) string {
	t.Helper()
	parsed, err := url.Parse(raw)
	if err != nil {
		t.Fatalf("parse avatar URL %q: %v", raw, err)
	}
	return parsed.RequestURI()
}

func avatarTestPNG(t *testing.T) []byte {
	t.Helper()
	img := image.NewNRGBA(image.Rect(0, 0, 16, 16))
	for y := 0; y < 16; y++ {
		for x := 0; x < 16; x++ {
			img.Set(x, y, color.NRGBA{R: 20, G: 100, B: 220, A: 255})
		}
	}
	var out bytes.Buffer
	if err := png.Encode(&out, img); err != nil {
		t.Fatalf("encode test PNG: %v", err)
	}
	return out.Bytes()
}
