//go:build !integration

package composite

import (
	"encoding/json"
	"testing"
)

func TestSubRequest_JSONSerialization(t *testing.T) {
	req := SubRequest{
		Method:      "POST",
		URL:         "/services/data/v58.0/sobjects/Account",
		ReferenceID: "ref1",
		Body:        json.RawMessage(`{"Name": "Test Account"}`),
		HttpHeaders: map[string]string{"Content-Type": "application/json"},
	}

	data, err := json.Marshal(req)
	if err != nil {
		t.Fatalf("Failed to marshal SubRequest: %v", err)
	}

	var unmarshaled SubRequest
	if err := json.Unmarshal(data, &unmarshaled); err != nil {
		t.Fatalf("Failed to unmarshal SubRequest: %v", err)
	}

	if unmarshaled.Method != req.Method {
		t.Errorf("Expected method %s, got %s", req.Method, unmarshaled.Method)
	}
	if unmarshaled.ReferenceID != req.ReferenceID {
		t.Errorf("Expected referenceId %s, got %s", req.ReferenceID, unmarshaled.ReferenceID)
	}
}

func TestBatchRequest_HaltOnError(t *testing.T) {
	req := BatchRequest{
		BatchRequests: []SubRequest{
			{Method: "POST", URL: "/test"},
		},
		HaltOnError: true,
	}

	data, err := json.Marshal(req)
	if err != nil {
		t.Fatalf("Failed to marshal BatchRequest: %v", err)
	}

	var m map[string]interface{}
	if err := json.Unmarshal(data, &m); err != nil {
		t.Fatalf("Failed to unmarshal to map: %v", err)
	}

	if m["haltOnError"] != true {
		t.Error("Expected haltOnError to be true")
	}
}

func TestBatchSubResult_ResultFieldName(t *testing.T) {
	resp := BatchResponse{
		HasErrors: true,
		Results: []BatchSubResult{
			{StatusCode: 200, ReferenceID: "ref1", Result: map[string]string{"id": "001xx"}},
			{StatusCode: 400, ReferenceID: "ref2", Result: map[string]string{"error": "bad request"}},
		},
	}

	data, err := json.Marshal(resp)
	if err != nil {
		t.Fatalf("Failed to marshal BatchResponse: %v", err)
	}

	// Verify the JSON field name is "result" (not "body")
	var raw map[string]interface{}
	if err := json.Unmarshal(data, &raw); err != nil {
		t.Fatalf("Failed to unmarshal to raw map: %v", err)
	}
	results := raw["results"].([]interface{})
	firstResult := results[0].(map[string]interface{})
	if _, ok := firstResult["result"]; !ok {
		t.Error("Expected batch result to have field 'result', not 'body'")
	}
	if _, ok := firstResult["body"]; ok {
		t.Error("Batch result should NOT have field 'body'")
	}

	var unmarshaled BatchResponse
	if err := json.Unmarshal(data, &unmarshaled); err != nil {
		t.Fatalf("Failed to unmarshal BatchResponse: %v", err)
	}

	if !unmarshaled.HasErrors {
		t.Error("Expected HasErrors to be true")
	}
	if len(unmarshaled.Results) != 2 {
		t.Errorf("Expected 2 results, got %d", len(unmarshaled.Results))
	}
}

func TestCompositeSubResult_BodyFieldName(t *testing.T) {
	result := CompositeSubResult{
		Body:           map[string]string{"id": "001xx", "success": "true"},
		HttpStatusCode: 201,
		ReferenceId:    "NewAccount",
		HttpHeaders:    map[string]string{"Location": "/services/data/v63.0/sobjects/Account/001xx"},
	}

	data, err := json.Marshal(result)
	if err != nil {
		t.Fatalf("Failed to marshal CompositeSubResult: %v", err)
	}

	// Verify the JSON field name is "body" (not "result")
	var raw map[string]interface{}
	if err := json.Unmarshal(data, &raw); err != nil {
		t.Fatalf("Failed to unmarshal to raw map: %v", err)
	}
	if _, ok := raw["body"]; !ok {
		t.Error("Expected composite result to have field 'body'")
	}
	if _, ok := raw["result"]; ok {
		t.Error("Composite result should NOT have field 'result'")
	}
	if _, ok := raw["httpStatusCode"]; !ok {
		t.Error("Expected composite result to have field 'httpStatusCode'")
	}
	if _, ok := raw["statusCode"]; ok {
		t.Error("Composite result should NOT have field 'statusCode'")
	}
}

func TestCompositeRequest_Roundtrip(t *testing.T) {
	req := CompositeRequest{
		AllOrNone: true,
		CompositeRequest: []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}"}`),
			},
		},
	}

	data, err := json.Marshal(req)
	if err != nil {
		t.Fatalf("Failed to marshal CompositeRequest: %v", err)
	}

	var unmarshaled CompositeRequest
	if err := json.Unmarshal(data, &unmarshaled); err != nil {
		t.Fatalf("Failed to unmarshal CompositeRequest: %v", err)
	}

	if !unmarshaled.AllOrNone {
		t.Error("Expected AllOrNone to be true")
	}
	if len(unmarshaled.CompositeRequest) != 2 {
		t.Errorf("Expected 2 subrequests, got %d", len(unmarshaled.CompositeRequest))
	}
	if unmarshaled.CompositeRequest[0].ReferenceId != "NewAccount" {
		t.Errorf("Expected referenceId 'NewAccount', got '%s'", unmarshaled.CompositeRequest[0].ReferenceId)
	}
}

func TestCompositeResponse_Roundtrip(t *testing.T) {
	resp := CompositeResponse{
		CompositeResponse: []CompositeSubResult{
			{
				HttpStatusCode: 201,
				ReferenceId:    "NewAccount",
				Body:           map[string]interface{}{"id": "001xx", "success": true},
			},
		},
	}

	data, err := json.Marshal(resp)
	if err != nil {
		t.Fatalf("Failed to marshal CompositeResponse: %v", err)
	}

	var unmarshaled CompositeResponse
	if err := json.Unmarshal(data, &unmarshaled); err != nil {
		t.Fatalf("Failed to unmarshal CompositeResponse: %v", err)
	}

	if len(unmarshaled.CompositeResponse) != 1 {
		t.Fatalf("Expected 1 result, got %d", len(unmarshaled.CompositeResponse))
	}
	if unmarshaled.CompositeResponse[0].HttpStatusCode != 201 {
		t.Errorf("Expected httpStatusCode 201, got %d", unmarshaled.CompositeResponse[0].HttpStatusCode)
	}
}

// ─────────────────────────────────────────────────────────────────────────────
// Wire Format Compliance Tests — Salesforce API v63.0
// Verify JSON key names match Salesforce exactly. Batch uses "result"/"statusCode",
// Composite uses "body"/"httpStatusCode". Mixing them is a spec violation.
// ─────────────────────────────────────────────────────────────────────────────

func TestBatchVsComposite_WireFormatDifference(t *testing.T) {
	// Batch: "result" + "statusCode"
	batchResult := BatchSubResult{
		StatusCode: 200, ReferenceID: "b1",
		Result: map[string]string{"id": "001"},
	}
	bData, err := json.Marshal(batchResult)
	if err != nil {
		t.Fatalf("marshal BatchSubResult: %v", err)
	}

	// Composite: "body" + "httpStatusCode"
	compositeResult := CompositeSubResult{
		HttpStatusCode: 200, ReferenceId: "c1",
		Body: map[string]string{"id": "001"},
	}
	cData, err := json.Marshal(compositeResult)
	if err != nil {
		t.Fatalf("marshal CompositeSubResult: %v", err)
	}

	var bMap, cMap map[string]interface{}
	json.Unmarshal(bData, &bMap)
	json.Unmarshal(cData, &cMap)

	// Batch must have "result" and "statusCode"
	if _, ok := bMap["result"]; !ok {
		t.Error("Batch must use 'result' field")
	}
	if _, ok := bMap["statusCode"]; !ok {
		t.Error("Batch must use 'statusCode' field")
	}
	if _, ok := bMap["body"]; ok {
		t.Error("Batch must NOT use 'body' field")
	}
	if _, ok := bMap["httpStatusCode"]; ok {
		t.Error("Batch must NOT use 'httpStatusCode' field")
	}

	// Composite must have "body" and "httpStatusCode"
	if _, ok := cMap["body"]; !ok {
		t.Error("Composite must use 'body' field")
	}
	if _, ok := cMap["httpStatusCode"]; !ok {
		t.Error("Composite must use 'httpStatusCode' field")
	}
	if _, ok := cMap["result"]; ok {
		t.Error("Composite must NOT use 'result' field")
	}
	if _, ok := cMap["statusCode"]; ok {
		t.Error("Composite must NOT use 'statusCode' field")
	}
}

func TestSubRequest_OmitEmptyFields(t *testing.T) {
	// When Body, ReferenceID, and HttpHeaders are zero values, they should be omitted
	req := SubRequest{
		Method: "GET",
		URL:    "/services/data/v63.0/sobjects/Account/001xx",
	}
	data, err := json.Marshal(req)
	if err != nil {
		t.Fatalf("marshal SubRequest: %v", err)
	}

	var raw map[string]interface{}
	json.Unmarshal(data, &raw)

	if _, ok := raw["body"]; ok {
		t.Error("body should be omitted when empty")
	}
	if _, ok := raw["referenceId"]; ok {
		t.Error("referenceId should be omitted when empty")
	}
	if _, ok := raw["httpHeaders"]; ok {
		t.Error("httpHeaders should be omitted when empty")
	}
	// method and url must always be present
	if _, ok := raw["method"]; !ok {
		t.Error("method must always be present")
	}
	if _, ok := raw["url"]; !ok {
		t.Error("url must always be present")
	}
}

func TestCompositeSubrequest_OmitEmptyFields(t *testing.T) {
	// Body and HttpHeaders are omitempty, referenceId is NOT omitempty
	req := CompositeSubrequest{
		Method:      "GET",
		URL:         "/services/data/v63.0/query",
		ReferenceId: "QueryRef",
	}
	data, err := json.Marshal(req)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var raw map[string]interface{}
	json.Unmarshal(data, &raw)

	if _, ok := raw["body"]; ok {
		t.Error("body should be omitted when empty")
	}
	if _, ok := raw["httpHeaders"]; ok {
		t.Error("httpHeaders should be omitted when empty")
	}
	// referenceId is required (no omitempty)
	if _, ok := raw["referenceId"]; !ok {
		t.Error("referenceId must always be present (no omitempty)")
	}
}

func TestBatchRequest_OmitHaltOnError(t *testing.T) {
	req := BatchRequest{
		BatchRequests: []SubRequest{{Method: "GET", URL: "/test"}},
		HaltOnError:   false,
	}
	data, err := json.Marshal(req)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var raw map[string]interface{}
	json.Unmarshal(data, &raw)

	// haltOnError is omitempty, so false should be omitted
	if _, ok := raw["haltOnError"]; ok {
		t.Error("haltOnError=false should be omitted (omitempty)")
	}
}

func TestBatchResponse_HasErrorsFalse(t *testing.T) {
	resp := BatchResponse{
		HasErrors: false,
		Results: []BatchSubResult{
			{StatusCode: 200, Result: "ok"},
		},
	}
	data, err := json.Marshal(resp)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var raw map[string]interface{}
	json.Unmarshal(data, &raw)

	// hasErrors should still be present even when false (no omitempty)
	if _, ok := raw["hasErrors"]; !ok {
		t.Error("hasErrors must always be present (no omitempty)")
	}
	if raw["hasErrors"] != false {
		t.Errorf("expected hasErrors=false, got %v", raw["hasErrors"])
	}
}

func TestBatchRequest_UnmarshalFromSalesforceWire(t *testing.T) {
	// Simulate a Salesforce-style batch request JSON
	wireJSON := `{
		"batchRequests": [
			{
				"method": "GET",
				"url": "v63.0/sobjects/Account/describe"
			},
			{
				"method": "POST",
				"url": "v63.0/sobjects/Account",
				"body": {"Name": "NewAccount"},
				"httpHeaders": {"Sforce-Auto-Assign": "FALSE"}
			}
		],
		"haltOnError": true
	}`

	var req BatchRequest
	if err := json.Unmarshal([]byte(wireJSON), &req); err != nil {
		t.Fatalf("unmarshal Salesforce wire format: %v", err)
	}

	if len(req.BatchRequests) != 2 {
		t.Fatalf("expected 2 batch requests, got %d", len(req.BatchRequests))
	}
	if !req.HaltOnError {
		t.Error("expected HaltOnError=true")
	}
	if req.BatchRequests[0].Method != "GET" {
		t.Errorf("expected GET, got %s", req.BatchRequests[0].Method)
	}
	if req.BatchRequests[1].HttpHeaders["Sforce-Auto-Assign"] != "FALSE" {
		t.Error("expected custom header to be preserved")
	}
	// Body should be raw JSON, not re-encoded
	if string(req.BatchRequests[1].Body) != `{"Name": "NewAccount"}` {
		t.Errorf("body should preserve raw JSON, got %s", string(req.BatchRequests[1].Body))
	}
}

func TestCompositeRequest_UnmarshalFromSalesforceWire(t *testing.T) {
	wireJSON := `{
		"allOrNone": true,
		"compositeRequest": [
			{
				"method": "POST",
				"url": "/services/data/v63.0/sobjects/Account",
				"referenceId": "NewAccount",
				"body": {"Name": "Acme"}
			},
			{
				"method": "GET",
				"url": "/services/data/v63.0/sobjects/Account/@{NewAccount.id}",
				"referenceId": "GetAccount"
			}
		]
	}`

	var req CompositeRequest
	if err := json.Unmarshal([]byte(wireJSON), &req); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if !req.AllOrNone {
		t.Error("AllOrNone should be true")
	}
	if len(req.CompositeRequest) != 2 {
		t.Fatalf("expected 2 subrequests, got %d", len(req.CompositeRequest))
	}
	if req.CompositeRequest[0].ReferenceId != "NewAccount" {
		t.Errorf("expected referenceId 'NewAccount', got '%s'", req.CompositeRequest[0].ReferenceId)
	}
	// GET request should have no body
	if len(req.CompositeRequest[1].Body) != 0 {
		t.Error("GET request should have empty body")
	}
}

func TestCompositeResponse_MultipleResults(t *testing.T) {
	resp := CompositeResponse{
		CompositeResponse: []CompositeSubResult{
			{HttpStatusCode: 201, ReferenceId: "Create", Body: map[string]interface{}{"id": "001xx", "success": true}},
			{HttpStatusCode: 200, ReferenceId: "Describe", Body: map[string]interface{}{"name": "Account"}},
			{HttpStatusCode: 400, ReferenceId: "BadReq", Body: []interface{}{map[string]interface{}{"errorCode": "INVALID", "message": "bad"}}},
		},
	}

	data, err := json.Marshal(resp)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var unmarshaled CompositeResponse
	if err := json.Unmarshal(data, &unmarshaled); err != nil {
		t.Fatalf("unmarshal: %v", err)
	}

	if len(unmarshaled.CompositeResponse) != 3 {
		t.Fatalf("expected 3 results, got %d", len(unmarshaled.CompositeResponse))
	}
	if unmarshaled.CompositeResponse[2].HttpStatusCode != 400 {
		t.Errorf("expected 400, got %d", unmarshaled.CompositeResponse[2].HttpStatusCode)
	}
	if unmarshaled.CompositeResponse[0].ReferenceId != "Create" {
		t.Errorf("expected 'Create', got '%s'", unmarshaled.CompositeResponse[0].ReferenceId)
	}
}

func TestBatchSubResult_NilResult(t *testing.T) {
	// Result can be nil (e.g., DELETE 204 No Content)
	result := BatchSubResult{
		StatusCode:  204,
		ReferenceID: "del1",
		Result:      nil,
	}
	data, err := json.Marshal(result)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var raw map[string]interface{}
	json.Unmarshal(data, &raw)

	// "result" should be present as null
	if _, ok := raw["result"]; !ok {
		t.Error("result field should be present even when nil (no omitempty)")
	}
}

func TestCompositeSubResult_NilBody(t *testing.T) {
	result := CompositeSubResult{
		HttpStatusCode: 204,
		ReferenceId:    "del1",
		Body:           nil,
	}
	data, err := json.Marshal(result)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var raw map[string]interface{}
	json.Unmarshal(data, &raw)

	// "body" should be present as null
	if _, ok := raw["body"]; !ok {
		t.Error("body field should be present even when nil (no omitempty)")
	}
}

func TestBatchSubResult_HttpHeadersOmitEmpty(t *testing.T) {
	result := BatchSubResult{
		StatusCode: 200,
		Result:     "ok",
	}
	data, err := json.Marshal(result)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var raw map[string]interface{}
	json.Unmarshal(data, &raw)

	if _, ok := raw["httpHeaders"]; ok {
		t.Error("httpHeaders should be omitted when nil")
	}
}

func TestCompositeSubResult_HttpHeadersOmitEmpty(t *testing.T) {
	result := CompositeSubResult{
		HttpStatusCode: 200,
		ReferenceId:    "ref",
		Body:           "ok",
	}
	data, err := json.Marshal(result)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}

	var raw map[string]interface{}
	json.Unmarshal(data, &raw)

	if _, ok := raw["httpHeaders"]; ok {
		t.Error("httpHeaders should be omitted when nil")
	}
}
