package rest

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

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/features/composite"
	"github.com/nexuscrm/backend/internal/interfaces/middleware"
)

func init() {
	gin.SetMode(gin.TestMode)
}

// ─────────────────────────────────────────────────────────────────────────────
// resolveReferences — Reference chaining (@{refId.field})
// ─────────────────────────────────────────────────────────────────────────────

func TestResolveReferences_SimpleField(t *testing.T) {
	results := map[string]interface{}{
		"NewAccount": map[string]interface{}{
			"id":      "001xx000003ABCDEF",
			"success": true,
		},
	}

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "simple field reference in body",
			input:    `{"AccountId": "@{NewAccount.id}"}`,
			expected: `{"AccountId": "001xx000003ABCDEF"}`,
		},
		{
			name:     "reference in URL",
			input:    "/services/data/v63.0/sobjects/Account/@{NewAccount.id}",
			expected: "/services/data/v63.0/sobjects/Account/001xx000003ABCDEF",
		},
		{
			name:     "boolean field",
			input:    `{"wasSuccess": "@{NewAccount.success}"}`,
			expected: `{"wasSuccess": "true"}`,
		},
		{
			name:     "no match — unknown ref",
			input:    `{"AccountId": "@{UnknownRef.id}"}`,
			expected: `{"AccountId": "@{UnknownRef.id}"}`,
		},
		{
			name:     "no match — no field path",
			input:    `{"AccountId": "@{NewAccount}"}`,
			expected: `{"AccountId": "@{NewAccount}"}`,
		},
		{
			name:     "no references at all",
			input:    `{"Name": "Acme Corp"}`,
			expected: `{"Name": "Acme Corp"}`,
		},
		{
			name:     "multiple references",
			input:    `{"AccountId": "@{NewAccount.id}", "IsSuccess": "@{NewAccount.success}"}`,
			expected: `{"AccountId": "001xx000003ABCDEF", "IsSuccess": "true"}`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := resolveReferences(tt.input, results)
			if got != tt.expected {
				t.Errorf("resolveReferences(%q) =\n  %q\nwant\n  %q", tt.input, got, tt.expected)
			}
		})
	}
}

func TestResolveReferences_NestedField(t *testing.T) {
	results := map[string]interface{}{
		"DescribeResult": map[string]interface{}{
			"objectDescribe": map[string]interface{}{
				"name":  "Account",
				"label": "Account",
			},
		},
	}

	input := "@{DescribeResult.objectDescribe.name}"
	expected := "Account"

	got := resolveReferences(input, results)
	if got != expected {
		t.Errorf("resolveReferences(%q) = %q, want %q", input, got, expected)
	}
}

func TestResolveReferences_EmptyResults(t *testing.T) {
	input := "@{Ref.field}"
	got := resolveReferences(input, nil)
	if got != input {
		t.Errorf("resolveReferences with nil results = %q, want %q", got, input)
	}

	got = resolveReferences(input, map[string]interface{}{})
	if got != input {
		t.Errorf("resolveReferences with empty results = %q, want %q", got, input)
	}
}

func TestResolveReferences_NumericValues(t *testing.T) {
	results := map[string]interface{}{
		"Ref": map[string]interface{}{
			"count":   float64(42),
			"decimal": float64(3.14),
		},
	}

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "integer float64",
			input:    "@{Ref.count}",
			expected: "42",
		},
		{
			name:     "decimal float64",
			input:    "@{Ref.decimal}",
			expected: "3.14",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := resolveReferences(tt.input, results)
			if got != tt.expected {
				t.Errorf("resolveReferences(%q) = %q, want %q", tt.input, got, tt.expected)
			}
		})
	}
}

func TestResolveReferences_ComplexValue(t *testing.T) {
	results := map[string]interface{}{
		"Ref": map[string]interface{}{
			"nested": map[string]interface{}{"key": "val"},
		},
	}

	got := resolveReferences("@{Ref.nested}", results)
	// Complex types get JSON-encoded
	if !strings.Contains(got, `"key"`) || !strings.Contains(got, `"val"`) {
		t.Errorf("expected JSON-encoded nested object, got %q", got)
	}
}

func TestResolveReferences_NoAtSymbol(t *testing.T) {
	results := map[string]interface{}{
		"Ref": map[string]interface{}{"id": "001"},
	}
	// No @{ in input — should short-circuit and return input unchanged
	input := `{"Name": "no refs here"}`
	got := resolveReferences(input, results)
	if got != input {
		t.Errorf("expected unchanged input, got %q", got)
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// extractField — Dot-notation field traversal
// ─────────────────────────────────────────────────────────────────────────────

func TestExtractField(t *testing.T) {
	data := map[string]interface{}{
		"id":      "001xx",
		"success": true,
		"nested": map[string]interface{}{
			"deep": "value",
		},
	}

	tests := []struct {
		path     string
		expected interface{}
	}{
		{"id", "001xx"},
		{"success", true},
		{"nested.deep", "value"},
		{"nonexistent", nil},
		{"nested.nonexistent", nil},
	}

	for _, tt := range tests {
		got := extractField(data, tt.path)
		if got != tt.expected {
			t.Errorf("extractField(%q) = %v, want %v", tt.path, got, tt.expected)
		}
	}
}

func TestExtractField_NonMapData(t *testing.T) {
	// When data is not a map, should return nil
	got := extractField("stringvalue", "field")
	if got != nil {
		t.Errorf("expected nil for non-map data, got %v", got)
	}

	got = extractField(42, "field")
	if got != nil {
		t.Errorf("expected nil for int data, got %v", got)
	}

	got = extractField(nil, "field")
	if got != nil {
		t.Errorf("expected nil for nil data, got %v", got)
	}
}

func TestExtractField_DeepNesting(t *testing.T) {
	data := map[string]interface{}{
		"a": map[string]interface{}{
			"b": map[string]interface{}{
				"c": map[string]interface{}{
					"d": "deepvalue",
				},
			},
		},
	}

	got := extractField(data, "a.b.c.d")
	if got != "deepvalue" {
		t.Errorf("expected 'deepvalue', got %v", got)
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// isCompositeURL — Recursion guard
// ─────────────────────────────────────────────────────────────────────────────

func TestIsCompositeURL(t *testing.T) {
	tests := []struct {
		url      string
		expected bool
	}{
		// Should BLOCK (infinite recursion)
		{"/services/data/v63.0/composite", true},
		{"/composite", true},
		{"/composite/", true}, // trailing slash
		{"/services/data/v63.0/composite/batch", true},
		{"/composite/batch", true},
		{"/composite/graph", true},
		{"/services/data/v63.0/composite?foo=bar", true},

		// Should ALLOW (safe targets)
		{"/composite/sobjects", false},
		{"/services/data/v63.0/composite/sobjects", false},
		{"/composite/tree/Account", false},
		{"/sobjects/Account/describe", false},
		{"/query?q=SELECT+Id+FROM+Account", false},
	}

	for _, tt := range tests {
		t.Run(tt.url, func(t *testing.T) {
			got := isCompositeURL(tt.url)
			if got != tt.expected {
				t.Errorf("isCompositeURL(%q) = %v, want %v", tt.url, got, tt.expected)
			}
		})
	}
}

func TestIsCompositeURL_EdgeCases(t *testing.T) {
	tests := []struct {
		name     string
		url      string
		expected bool
	}{
		{"empty string", "", false},
		{"just slash", "/", false},
		{"composite in path segment", "/something/composite/other", false},
		{"composite/batch with query", "/composite/batch?x=1", true},
		{"composite/graph with query", "/composite/graph?x=1", true},
		{"double trailing slash blocked (TrimRight normalizes to /composite)", "/composite//", true},
		{"composite/sobjects subpath", "/composite/sobjects/Account", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := isCompositeURL(tt.url)
			if got != tt.expected {
				t.Errorf("isCompositeURL(%q) = %v, want %v", tt.url, got, tt.expected)
			}
		})
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// ResponseWriter — Captures internal subrequest responses
// ─────────────────────────────────────────────────────────────────────────────

func TestNewResponseWriter_Defaults(t *testing.T) {
	w := NewResponseWriter()

	if w.StatusCode != 200 {
		t.Errorf("expected default StatusCode 200, got %d", w.StatusCode)
	}
	if w.Body == nil {
		t.Fatal("Body buffer should be initialized")
	}
	if w.Body.Len() != 0 {
		t.Errorf("expected empty body, got %d bytes", w.Body.Len())
	}
	if w.HeaderMap == nil {
		t.Fatal("HeaderMap should be initialized")
	}
}

func TestResponseWriter_Write(t *testing.T) {
	w := NewResponseWriter()

	n, err := w.Write([]byte(`{"id":"001xx"}`))
	if err != nil {
		t.Fatalf("Write error: %v", err)
	}
	if n != 14 {
		t.Errorf("expected 14 bytes written, got %d", n)
	}
	if w.Body.String() != `{"id":"001xx"}` {
		t.Errorf("unexpected body: %s", w.Body.String())
	}
}

func TestResponseWriter_WriteString(t *testing.T) {
	w := NewResponseWriter()

	n, err := w.WriteString("hello world")
	if err != nil {
		t.Fatalf("WriteString error: %v", err)
	}
	if n != 11 {
		t.Errorf("expected 11 bytes, got %d", n)
	}
	if w.Body.String() != "hello world" {
		t.Errorf("unexpected body: %s", w.Body.String())
	}
}

func TestResponseWriter_WriteHeader(t *testing.T) {
	w := NewResponseWriter()
	w.WriteHeader(404)

	if w.StatusCode != 404 {
		t.Errorf("expected 404, got %d", w.StatusCode)
	}
}

func TestResponseWriter_Header(t *testing.T) {
	w := NewResponseWriter()
	w.Header().Set("X-Custom", "value")

	if w.Header().Get("X-Custom") != "value" {
		t.Errorf("expected header 'X-Custom: value', got %q", w.Header().Get("X-Custom"))
	}
}

func TestResponseWriter_MultipleWrites(t *testing.T) {
	w := NewResponseWriter()
	w.Write([]byte("chunk1"))
	w.Write([]byte("chunk2"))

	if w.Body.String() != "chunk1chunk2" {
		t.Errorf("expected concatenated body, got %q", w.Body.String())
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// convertHeaders — http.Header to map[string]string
// ─────────────────────────────────────────────────────────────────────────────

func TestConvertHeaders(t *testing.T) {
	tests := []struct {
		name     string
		input    http.Header
		expected map[string]string
	}{
		{
			name:     "empty headers",
			input:    http.Header{},
			expected: map[string]string{},
		},
		{
			name: "single value headers",
			input: http.Header{
				"Content-Type": []string{"application/json"},
				"X-Custom":     []string{"test"},
			},
			expected: map[string]string{
				"Content-Type": "application/json",
				"X-Custom":     "test",
			},
		},
		{
			name: "multi-value header takes first",
			input: http.Header{
				"Accept": []string{"application/json", "text/html"},
			},
			expected: map[string]string{
				"Accept": "application/json",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := convertHeaders(tt.input)
			if len(got) != len(tt.expected) {
				t.Errorf("expected %d headers, got %d", len(tt.expected), len(got))
			}
			for k, v := range tt.expected {
				if got[k] != v {
					t.Errorf("header %q: expected %q, got %q", k, v, got[k])
				}
			}
		})
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// CompositeBatchHandler — HandleBatch
// ─────────────────────────────────────────────────────────────────────────────

// setupBatchTestRouter creates a gin.Engine with a mock echo endpoint and the batch handler.
func setupBatchTestRouter() *gin.Engine {
	r := gin.New()

	// Mock endpoint that echoes back request info
	echoHandler := func(c *gin.Context) {
		var body interface{}
		if c.Request.Body != nil {
			json.NewDecoder(c.Request.Body).Decode(&body)
		}
		c.JSON(http.StatusOK, gin.H{
			"method": c.Request.Method,
			"url":    c.Request.URL.Path,
			"body":   body,
		})
	}

	// Register routes that subrequests will target
	r.GET("/services/data/v63.0/sobjects/Account/describe", echoHandler)
	r.POST("/services/data/v63.0/sobjects/Account", echoHandler)
	r.GET("/services/data/v63.0/sobjects/Account/:id", echoHandler)
	r.PATCH("/services/data/v63.0/sobjects/Account/:id", echoHandler)
	r.DELETE("/services/data/v63.0/sobjects/Account/:id", echoHandler)

	// Register the batch handler
	batchHandler := NewCompositeBatchHandler(r)
	r.POST("/services/data/:version/composite/batch", batchHandler.HandleBatch)

	return r
}

func TestHandleBatch_SingleGET(t *testing.T) {
	r := setupBatchTestRouter()

	reqBody := composite.BatchRequest{
		BatchRequests: []composite.SubRequest{
			{Method: "GET", URL: "/services/data/v63.0/sobjects/Account/describe", ReferenceID: "descRef"},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	var resp composite.BatchResponse
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if resp.HasErrors {
		t.Error("expected HasErrors=false for successful request")
	}
	if len(resp.Results) != 1 {
		t.Fatalf("expected 1 result, got %d", len(resp.Results))
	}
	if resp.Results[0].StatusCode != 200 {
		t.Errorf("expected status 200, got %d", resp.Results[0].StatusCode)
	}
	if resp.Results[0].ReferenceID != "descRef" {
		t.Errorf("expected referenceId 'descRef', got '%s'", resp.Results[0].ReferenceID)
	}
}

func TestHandleBatch_MultipleSubrequests(t *testing.T) {
	r := setupBatchTestRouter()

	reqBody := composite.BatchRequest{
		BatchRequests: []composite.SubRequest{
			{Method: "GET", URL: "/services/data/v63.0/sobjects/Account/describe", ReferenceID: "ref1"},
			{Method: "POST", URL: "/services/data/v63.0/sobjects/Account", ReferenceID: "ref2", Body: json.RawMessage(`{"Name":"Acme"}`)},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}

	var resp composite.BatchResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if len(resp.Results) != 2 {
		t.Fatalf("expected 2 results, got %d", len(resp.Results))
	}
	if resp.Results[0].ReferenceID != "ref1" {
		t.Errorf("first result referenceId mismatch")
	}
	if resp.Results[1].ReferenceID != "ref2" {
		t.Errorf("second result referenceId mismatch")
	}
}

func TestHandleBatch_InvalidJSON(t *testing.T) {
	r := setupBatchTestRouter()

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", strings.NewReader("{invalid"))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestHandleBatch_ExceedsMaxSubrequests(t *testing.T) {
	r := setupBatchTestRouter()

	subs := make([]composite.SubRequest, 26)
	for i := range subs {
		subs[i] = composite.SubRequest{Method: "GET", URL: "/services/data/v63.0/sobjects/Account/describe"}
	}

	reqBody := composite.BatchRequest{BatchRequests: subs}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400 for >25 subrequests, got %d", w.Code)
	}
}

func TestHandleBatch_Exactly25SubrequestsAllowed(t *testing.T) {
	r := setupBatchTestRouter()

	subs := make([]composite.SubRequest, 25)
	for i := range subs {
		subs[i] = composite.SubRequest{Method: "GET", URL: "/services/data/v63.0/sobjects/Account/describe"}
	}

	reqBody := composite.BatchRequest{BatchRequests: subs}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Errorf("expected 200 for exactly 25 subrequests, got %d: %s", w.Code, w.Body.String())
	}

	var resp composite.BatchResponse
	json.Unmarshal(w.Body.Bytes(), &resp)
	if len(resp.Results) != 25 {
		t.Errorf("expected 25 results, got %d", len(resp.Results))
	}
}

func TestHandleBatch_HaltOnError(t *testing.T) {
	r := gin.New()

	// First route succeeds, second returns 404 (no route), third would succeed
	r.GET("/services/data/v63.0/ok", func(c *gin.Context) {
		c.JSON(200, gin.H{"status": "ok"})
	})
	r.GET("/services/data/v63.0/also-ok", func(c *gin.Context) {
		c.JSON(200, gin.H{"status": "also ok"})
	})
	// No route for /services/data/v63.0/notfound — will 404

	batchHandler := NewCompositeBatchHandler(r)
	r.POST("/services/data/:version/composite/batch", batchHandler.HandleBatch)

	reqBody := composite.BatchRequest{
		HaltOnError: true,
		BatchRequests: []composite.SubRequest{
			{Method: "GET", URL: "/services/data/v63.0/ok", ReferenceID: "first"},
			{Method: "GET", URL: "/services/data/v63.0/notfound", ReferenceID: "second"},
			{Method: "GET", URL: "/services/data/v63.0/also-ok", ReferenceID: "third"},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}

	var resp composite.BatchResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if !resp.HasErrors {
		t.Error("expected HasErrors=true when a subrequest fails")
	}

	// With haltOnError, the third request should not have been executed.
	// The third result slot should have zero-value StatusCode (0) since the
	// Results slice is pre-allocated but the third wasn't populated.
	if len(resp.Results) != 3 {
		t.Fatalf("Results slice should be pre-allocated to 3, got %d", len(resp.Results))
	}
	if resp.Results[0].StatusCode != 200 {
		t.Errorf("first request should succeed, got %d", resp.Results[0].StatusCode)
	}
	// Second request should have an error status (404)
	if resp.Results[1].StatusCode < 400 {
		t.Errorf("second request should fail, got %d", resp.Results[1].StatusCode)
	}
	// Third result was never populated (halt) — StatusCode stays at 0
	if resp.Results[2].StatusCode != 0 {
		t.Errorf("third request should not have been executed (halt), got status %d", resp.Results[2].StatusCode)
	}
}

func TestHandleBatch_AuthHeaderForwarding(t *testing.T) {
	r := gin.New()

	// Endpoint that captures the Authorization header
	r.GET("/services/data/v63.0/check-auth", func(c *gin.Context) {
		authHeader := c.GetHeader("Authorization")
		c.JSON(200, gin.H{"auth": authHeader})
	})

	batchHandler := NewCompositeBatchHandler(r)
	r.POST("/services/data/:version/composite/batch", batchHandler.HandleBatch)

	reqBody := composite.BatchRequest{
		BatchRequests: []composite.SubRequest{
			{Method: "GET", URL: "/services/data/v63.0/check-auth"},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer test-token-12345")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.BatchResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if len(resp.Results) != 1 {
		t.Fatalf("expected 1 result, got %d", len(resp.Results))
	}

	resultMap, ok := resp.Results[0].Result.(map[string]interface{})
	if !ok {
		t.Fatalf("expected map result, got %T", resp.Results[0].Result)
	}
	if resultMap["auth"] != "Bearer test-token-12345" {
		t.Errorf("Authorization header not forwarded, got %q", resultMap["auth"])
	}
}

func TestHandleBatch_SubrequestCustomHeaders(t *testing.T) {
	r := gin.New()

	r.GET("/services/data/v63.0/check-headers", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"sforce-auto-assign": c.GetHeader("Sforce-Auto-Assign"),
			"content-type":       c.GetHeader("Content-Type"),
		})
	})

	batchHandler := NewCompositeBatchHandler(r)
	r.POST("/services/data/:version/composite/batch", batchHandler.HandleBatch)

	reqBody := composite.BatchRequest{
		BatchRequests: []composite.SubRequest{
			{
				Method:      "GET",
				URL:         "/services/data/v63.0/check-headers",
				HttpHeaders: map[string]string{"Sforce-Auto-Assign": "FALSE"},
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.BatchResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	resultMap, ok := resp.Results[0].Result.(map[string]interface{})
	if !ok {
		t.Fatalf("expected map result, got %T", resp.Results[0].Result)
	}
	if resultMap["sforce-auto-assign"] != "FALSE" {
		t.Errorf("custom header not forwarded: got %q", resultMap["sforce-auto-assign"])
	}
}

func TestHandleBatch_RelativeURL(t *testing.T) {
	r := gin.New()

	r.GET("/services/data/v63.0/sobjects/Account/describe", func(c *gin.Context) {
		c.JSON(200, gin.H{"matched": true})
	})

	batchHandler := NewCompositeBatchHandler(r)
	r.POST("/services/data/:version/composite/batch", batchHandler.HandleBatch)

	// Use relative URL without leading slash
	reqBody := composite.BatchRequest{
		BatchRequests: []composite.SubRequest{
			{Method: "GET", URL: "sobjects/Account/describe"},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.BatchResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if resp.Results[0].StatusCode != 200 {
		t.Errorf("relative URL should be resolved to /services/data/v63.0/..., got status %d", resp.Results[0].StatusCode)
	}
}

func TestHandleBatch_RecordError(t *testing.T) {
	handler := &CompositeBatchHandler{}
	resp := &composite.BatchResponse{
		Results: make([]composite.BatchSubResult, 3),
	}

	handler.recordError(resp, 1, "badRef", http.StatusBadRequest, "something went wrong")

	if !resp.HasErrors {
		t.Error("HasErrors should be true after recordError")
	}
	if resp.Results[1].StatusCode != 400 {
		t.Errorf("expected status 400, got %d", resp.Results[1].StatusCode)
	}
	if resp.Results[1].ReferenceID != "badRef" {
		t.Errorf("expected referenceID 'badRef', got '%s'", resp.Results[1].ReferenceID)
	}
	// Result should be array of error objects
	errSlice, ok := resp.Results[1].Result.([]map[string]string)
	if !ok {
		t.Fatalf("expected []map[string]string, got %T", resp.Results[1].Result)
	}
	if errSlice[0]["errorCode"] != "INVALID_REQUEST" {
		t.Errorf("expected errorCode INVALID_REQUEST, got %s", errSlice[0]["errorCode"])
	}
	if errSlice[0]["message"] != "something went wrong" {
		t.Errorf("expected error message, got %s", errSlice[0]["message"])
	}
}

func TestHandleBatch_ResponseWireFormat(t *testing.T) {
	r := setupBatchTestRouter()

	reqBody := composite.BatchRequest{
		BatchRequests: []composite.SubRequest{
			{Method: "GET", URL: "/services/data/v63.0/sobjects/Account/describe", ReferenceID: "ref1"},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	// Verify raw JSON wire format uses correct field names
	var raw map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &raw)

	if _, ok := raw["hasErrors"]; !ok {
		t.Error("batch response must have 'hasErrors' field")
	}
	results, ok := raw["results"].([]interface{})
	if !ok {
		t.Fatal("batch response must have 'results' array")
	}
	firstResult := results[0].(map[string]interface{})
	if _, ok := firstResult["statusCode"]; !ok {
		t.Error("batch result must use 'statusCode' (not 'httpStatusCode')")
	}
	if _, ok := firstResult["result"]; !ok {
		t.Error("batch result must use 'result' (not 'body')")
	}
}

func TestHandleBatch_EmptyBatchRequests(t *testing.T) {
	r := setupBatchTestRouter()

	reqBody := composite.BatchRequest{
		BatchRequests: []composite.SubRequest{},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite/batch", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200 for empty batch, got %d", w.Code)
	}

	var resp composite.BatchResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if resp.HasErrors {
		t.Error("empty batch should not have errors")
	}
	if len(resp.Results) != 0 {
		t.Errorf("expected 0 results, got %d", len(resp.Results))
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// CompositeHandler — HandleComposite
// ─────────────────────────────────────────────────────────────────────────────

// setupCompositeTestRouter creates a gin.Engine with mock endpoints and the composite handler.
func setupCompositeTestRouter() *gin.Engine {
	r := gin.New()

	// Mock endpoint: creates an account and returns an ID
	r.POST("/services/data/v63.0/sobjects/Account", func(c *gin.Context) {
		var body map[string]interface{}
		json.NewDecoder(c.Request.Body).Decode(&body)
		c.JSON(http.StatusCreated, gin.H{
			"id":      "001xx000003ABCDEF",
			"success": true,
			"errors":  []interface{}{},
		})
	})

	// Mock endpoint: creates a contact
	r.POST("/services/data/v63.0/sobjects/Contact", func(c *gin.Context) {
		var body map[string]interface{}
		json.NewDecoder(c.Request.Body).Decode(&body)
		c.JSON(http.StatusCreated, gin.H{
			"id":        "003xx000004GHIJKL",
			"success":   true,
			"errors":    []interface{}{},
			"accountId": body["AccountId"],
		})
	})

	// Mock endpoint: GET describe
	r.GET("/services/data/v63.0/sobjects/Account/describe", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"name":  "Account",
			"label": "Account",
		})
	})

	// Mock endpoint: GET a record
	r.GET("/services/data/v63.0/sobjects/Account/:id", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"Id":   c.Param("id"),
			"Name": "Acme Corp",
		})
	})

	// Error endpoint
	r.GET("/services/data/v63.0/error", func(c *gin.Context) {
		c.JSON(http.StatusNotFound, gin.H{"errorCode": "NOT_FOUND", "message": "not found"})
	})

	compositeHandler := NewCompositeHandler(r)
	r.POST("/services/data/:version/composite", compositeHandler.HandleComposite)

	return r
}

func TestHandleComposite_SingleSubrequest(t *testing.T) {
	r := setupCompositeTestRouter()

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "GET",
				URL:         "/services/data/v63.0/sobjects/Account/describe",
				ReferenceId: "DescRef",
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if len(resp.CompositeResponse) != 1 {
		t.Fatalf("expected 1 result, got %d", len(resp.CompositeResponse))
	}
	if resp.CompositeResponse[0].HttpStatusCode != 200 {
		t.Errorf("expected 200, got %d", resp.CompositeResponse[0].HttpStatusCode)
	}
	if resp.CompositeResponse[0].ReferenceId != "DescRef" {
		t.Errorf("referenceId mismatch")
	}
}

func TestHandleComposite_ReferenceChaining(t *testing.T) {
	r := setupCompositeTestRouter()

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/sobjects/Account",
				ReferenceId: "NewAccount",
				Body:        json.RawMessage(`{"Name":"Acme"}`),
			},
			{
				Method:      "GET",
				URL:         "/services/data/v63.0/sobjects/Account/@{NewAccount.id}",
				ReferenceId: "GetAccount",
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d: %s", w.Code, w.Body.String())
	}

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if len(resp.CompositeResponse) != 2 {
		t.Fatalf("expected 2 results, got %d", len(resp.CompositeResponse))
	}

	// First: account creation returns 201
	if resp.CompositeResponse[0].HttpStatusCode != 201 {
		t.Errorf("expected 201 for account creation, got %d", resp.CompositeResponse[0].HttpStatusCode)
	}

	// Second: GET resolved @{NewAccount.id} to "001xx000003ABCDEF"
	if resp.CompositeResponse[1].HttpStatusCode != 200 {
		t.Errorf("expected 200 for GET, got %d", resp.CompositeResponse[1].HttpStatusCode)
	}
	getBody, ok := resp.CompositeResponse[1].Body.(map[string]interface{})
	if !ok {
		t.Fatalf("expected map body, got %T", resp.CompositeResponse[1].Body)
	}
	if getBody["Id"] != "001xx000003ABCDEF" {
		t.Errorf("expected resolved ID in GET response, got %v", getBody["Id"])
	}
}

func TestHandleComposite_BodyReferenceChaining(t *testing.T) {
	r := setupCompositeTestRouter()

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/sobjects/Account",
				ReferenceId: "NewAccount",
				Body:        json.RawMessage(`{"Name":"Acme"}`),
			},
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/sobjects/Contact",
				ReferenceId: "NewContact",
				Body:        json.RawMessage(`{"LastName":"Smith","AccountId":"@{NewAccount.id}"}`),
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if len(resp.CompositeResponse) != 2 {
		t.Fatalf("expected 2 results, got %d", len(resp.CompositeResponse))
	}

	// The contact creation should have received the resolved AccountId
	contactBody, ok := resp.CompositeResponse[1].Body.(map[string]interface{})
	if !ok {
		t.Fatalf("expected map body for contact, got %T", resp.CompositeResponse[1].Body)
	}
	if contactBody["accountId"] != "001xx000003ABCDEF" {
		t.Errorf("expected resolved AccountId, got %v", contactBody["accountId"])
	}
}

func TestHandleComposite_InvalidJSON(t *testing.T) {
	r := setupCompositeTestRouter()

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", strings.NewReader("not json"))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400, got %d", w.Code)
	}
}

func TestHandleComposite_ExceedsMaxSubrequests(t *testing.T) {
	r := setupCompositeTestRouter()

	subs := make([]composite.CompositeSubrequest, 26)
	for i := range subs {
		subs[i] = composite.CompositeSubrequest{
			Method:      "GET",
			URL:         "/services/data/v63.0/sobjects/Account/describe",
			ReferenceId: "ref",
		}
	}

	reqBody := composite.CompositeRequest{CompositeRequest: subs}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400 for >25 subrequests, got %d", w.Code)
	}
}

func TestHandleComposite_CompositeWithinCompositeBlocked(t *testing.T) {
	r := setupCompositeTestRouter()

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/composite",
				ReferenceId: "Recursive",
				Body:        json.RawMessage(`{}`),
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200 (error in subrequest, not top-level), got %d", w.Code)
	}

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if len(resp.CompositeResponse) != 1 {
		t.Fatalf("expected 1 result, got %d", len(resp.CompositeResponse))
	}
	if resp.CompositeResponse[0].HttpStatusCode != http.StatusBadRequest {
		t.Errorf("expected 400 for recursive composite, got %d", resp.CompositeResponse[0].HttpStatusCode)
	}
}

func TestHandleComposite_CompositeBatchBlocked(t *testing.T) {
	r := setupCompositeTestRouter()

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/composite/batch",
				ReferenceId: "BatchInComposite",
				Body:        json.RawMessage(`{}`),
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if resp.CompositeResponse[0].HttpStatusCode != http.StatusBadRequest {
		t.Errorf("expected 400 for /composite/batch within composite, got %d", resp.CompositeResponse[0].HttpStatusCode)
	}
}

func TestHandleComposite_AllOrNone_StopsOnError(t *testing.T) {
	r := setupCompositeTestRouter()

	reqBody := composite.CompositeRequest{
		AllOrNone: true,
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/sobjects/Account",
				ReferenceId: "Create",
				Body:        json.RawMessage(`{"Name":"Acme"}`),
			},
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/composite", // blocked
				ReferenceId: "Recursive",
				Body:        json.RawMessage(`{}`),
			},
			{
				Method:      "GET",
				URL:         "/services/data/v63.0/sobjects/Account/describe",
				ReferenceId: "ShouldNotRun",
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	// With allOrNone, should stop after the blocked composite URL
	if len(resp.CompositeResponse) != 2 {
		t.Fatalf("expected 2 results (stopped at blocked URL), got %d", len(resp.CompositeResponse))
	}
}

func TestHandleComposite_AllOrNone_StopsOnHTTPError(t *testing.T) {
	r := setupCompositeTestRouter()

	reqBody := composite.CompositeRequest{
		AllOrNone: true,
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "GET",
				URL:         "/services/data/v63.0/error",
				ReferenceId: "Err",
			},
			{
				Method:      "GET",
				URL:         "/services/data/v63.0/sobjects/Account/describe",
				ReferenceId: "ShouldNotRun",
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	// allOrNone: stops after 404 error
	if len(resp.CompositeResponse) != 1 {
		t.Fatalf("expected 1 result (allOrNone stops on error), got %d", len(resp.CompositeResponse))
	}
	if resp.CompositeResponse[0].HttpStatusCode != 404 {
		t.Errorf("expected 404, got %d", resp.CompositeResponse[0].HttpStatusCode)
	}
}

func TestHandleComposite_ResponseWireFormat(t *testing.T) {
	r := setupCompositeTestRouter()

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "GET",
				URL:         "/services/data/v63.0/sobjects/Account/describe",
				ReferenceId: "ref1",
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	// Verify raw JSON wire format
	var raw map[string]interface{}
	json.Unmarshal(w.Body.Bytes(), &raw)

	if _, ok := raw["compositeResponse"]; !ok {
		t.Error("composite response must have 'compositeResponse' field")
	}
	results := raw["compositeResponse"].([]interface{})
	firstResult := results[0].(map[string]interface{})
	if _, ok := firstResult["httpStatusCode"]; !ok {
		t.Error("composite result must use 'httpStatusCode' (not 'statusCode')")
	}
	if _, ok := firstResult["body"]; !ok {
		t.Error("composite result must use 'body' (not 'result')")
	}
	if _, ok := firstResult["referenceId"]; !ok {
		t.Error("composite result must have 'referenceId'")
	}
}

func TestHandleComposite_EmptySubrequests(t *testing.T) {
	r := setupCompositeTestRouter()

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", w.Code)
	}

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if len(resp.CompositeResponse) != 0 {
		t.Errorf("expected 0 results, got %d", len(resp.CompositeResponse))
	}
}

func TestHandleComposite_SobjectsAllowed(t *testing.T) {
	r := gin.New()

	// /composite/sobjects should NOT be blocked (it's a collections endpoint)
	r.POST("/services/data/v63.0/composite/sobjects", func(c *gin.Context) {
		c.JSON(200, gin.H{"ok": true})
	})

	compositeHandler := NewCompositeHandler(r)
	r.POST("/services/data/:version/composite", compositeHandler.HandleComposite)

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/composite/sobjects",
				ReferenceId: "Collections",
				Body:        json.RawMessage(`{"records":[]}`),
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if resp.CompositeResponse[0].HttpStatusCode != 200 {
		t.Errorf("composite/sobjects should be allowed, got status %d", resp.CompositeResponse[0].HttpStatusCode)
	}
}

func TestHandleComposite_TreeAllowed(t *testing.T) {
	r := gin.New()

	r.POST("/services/data/v63.0/composite/tree/Account", func(c *gin.Context) {
		c.JSON(201, gin.H{"hasErrors": false})
	})

	compositeHandler := NewCompositeHandler(r)
	r.POST("/services/data/:version/composite", compositeHandler.HandleComposite)

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/composite/tree/Account",
				ReferenceId: "Tree",
				Body:        json.RawMessage(`{"records":[]}`),
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	if resp.CompositeResponse[0].HttpStatusCode != 201 {
		t.Errorf("composite/tree should be allowed, got status %d", resp.CompositeResponse[0].HttpStatusCode)
	}
}

func TestHandleComposite_NonJSONResponseBody(t *testing.T) {
	r := gin.New()

	r.GET("/services/data/v63.0/plain", func(c *gin.Context) {
		c.String(200, "plain text response")
	})

	compositeHandler := NewCompositeHandler(r)
	r.POST("/services/data/:version/composite", compositeHandler.HandleComposite)

	reqBody := composite.CompositeRequest{
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "GET",
				URL:         "/services/data/v63.0/plain",
				ReferenceId: "PlainText",
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	var resp composite.CompositeResponse
	json.Unmarshal(w.Body.Bytes(), &resp)

	// Non-JSON body should fall back to string
	bodyStr, ok := resp.CompositeResponse[0].Body.(string)
	if !ok {
		t.Fatalf("expected string body for non-JSON response, got %T", resp.CompositeResponse[0].Body)
	}
	if bodyStr != "plain text response" {
		t.Errorf("expected 'plain text response', got %q", bodyStr)
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// Issue #305: Composite POST sub-requests must pass CSRF middleware
// ─────────────────────────────────────────────────────────────────────────────

// setupCompositeTestRouterWithCSRF creates a router with CSRF middleware enabled,
// reproducing the production setup where POST sub-requests are blocked.
func setupCompositeTestRouterWithCSRF() *gin.Engine {
	r := gin.New()

	// Apply CSRF middleware globally — same as production (main.go)
	r.Use(middleware.CSRF())

	// Mock ObjectPermissions POST endpoint
	r.POST("/services/data/v63.0/sobjects/ObjectPermissions", func(c *gin.Context) {
		var body map[string]interface{}
		json.NewDecoder(c.Request.Body).Decode(&body)
		c.JSON(http.StatusCreated, gin.H{
			"id":      "1100x000001AAAAA",
			"success": true,
			"errors":  []interface{}{},
		})
	})

	compositeHandler := NewCompositeHandler(r)
	r.POST("/services/data/:version/composite", compositeHandler.HandleComposite)

	return r
}

func TestHandleComposite_POSTSubrequestPassesCSRF(t *testing.T) {
	t.Setenv("CORS_ALLOWED_ORIGINS", "http://localhost:3000")
	t.Setenv("ALLOWED_ORIGINS", "")

	r := setupCompositeTestRouterWithCSRF()

	reqBody := composite.CompositeRequest{
		AllOrNone: true,
		CompositeRequest: []composite.CompositeSubrequest{
			{
				Method:      "POST",
				URL:         "/services/data/v63.0/sobjects/ObjectPermissions",
				ReferenceId: "refNew0",
				Body:        json.RawMessage(`{"ParentId":"standard_user","SobjectType":"monthly_arr__c","PermissionsRead":true}`),
			},
		},
	}
	body, _ := json.Marshal(reqBody)

	req, _ := http.NewRequest("POST", "/services/data/v63.0/composite", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	// Set Origin header — simulates a real browser request that passes CSRF on the outer request
	req.Header.Set("Origin", "http://localhost:3000")

	w := httptest.NewRecorder()
	r.ServeHTTP(w, req)

	if w.Code != http.StatusOK {
		t.Fatalf("outer composite request should return 200, got %d: %s", w.Code, w.Body.String())
	}

	var resp composite.CompositeResponse
	if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
		t.Fatalf("unmarshal composite response: %v", err)
	}

	if len(resp.CompositeResponse) != 1 {
		t.Fatalf("expected 1 sub-response, got %d", len(resp.CompositeResponse))
	}

	sub := resp.CompositeResponse[0]
	if sub.HttpStatusCode == 403 {
		t.Errorf("POST sub-request returned 403 (CSRF blocked) — Origin header was not forwarded to internal sub-request")
	}
	if sub.HttpStatusCode != 201 {
		t.Errorf("expected sub-request status 201 (Created), got %d", sub.HttpStatusCode)
	}
}
