//go:build integration

package rest_test

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// TestCompositeHandler_UpsertRecords_Strict verifies Upsert logic including numeric IDs and security
func TestCompositeHandler_UpsertRecords_Strict(t *testing.T) {
	sm, handlers, r := SetupHandlerHelper(t)

	// Register Routes
	// PATCH /services/data/v63.0/composite/sobjects/:sobject/:fieldName
	r.PATCH("/services/data/v63.0/composite/sobjects/:sObjectName/:externalIdFieldName", handlers.CompositeCollection.HandleCollectionsUpsert)
	// GET /services/data/v63.0/sobjects/:sobject/:id (Verify)
	r.GET("/services/data/v63.0/sobjects/:sobject/:id", handlers.SObject.GetRecord)

	// 1. Create Schema with String and Numeric External IDs
	ctx := context.Background()
	unix := time.Now().UnixNano()
	objName := fmt.Sprintf("UpsertStrict_%d", unix)
	meta := &models.ObjectMetadata{
		APIName:  objName,
		Label:    fmt.Sprintf("Upsert Strict %d", unix),
		IsCustom: true,
		Fields: []models.FieldMetadata{
			{APIName: "Name", Type: constants.FieldTypeString, Label: "Name", IsNameField: true},
			{APIName: "ExtStr__c", Type: constants.FieldTypeString, Label: "External String", IsUnique: true},
			{APIName: "ExtNum__c", Type: constants.FieldTypeDouble, Label: "External Num", IsUnique: true},
		},
	}
	require.NoError(t, sm.Metadata.CreateSchema(ctx, meta))
	sm.Metadata.RefreshCache(ctx)

	// =========================================================================
	// Scenario A: Upsert via String External ID
	// =========================================================================
	t.Run("String External ID", func(t *testing.T) {
		extId := fmt.Sprintf("EXT_%d", unix)

		// Insert
		payload := map[string]interface{}{
			"allOrNone": true,
			"records": []map[string]interface{}{
				{"ExtStr__c": extId, "Name": "String Insert"},
			},
		}
		body, _ := json.Marshal(payload)
		req, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/services/data/v63.0/composite/sobjects/%s/ExtStr__c", objName), bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		if w.Code != http.StatusOK {
			t.Logf("Upsert String Failed: %s", w.Body.String())
		}
		assert.Equal(t, http.StatusOK, w.Code)
		var res []map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &res)
		var id string
		if len(res) > 0 {
			if success, ok := res[0]["success"].(bool); !ok || !success {
				t.Logf("Upsert Response Error: %v", res[0])
			} else {
				id = res[0]["id"].(string)
			}
		}

		// Update (same External ID)
		payload["records"] = []map[string]interface{}{
			{"ExtStr__c": extId, "Name": "String Update"},
		}
		body, _ = json.Marshal(payload)
		req, _ = http.NewRequest(http.MethodPatch, fmt.Sprintf("/services/data/v63.0/composite/sobjects/%s/ExtStr__c", objName), bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		w = httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		json.Unmarshal(w.Body.Bytes(), &res)
		require.Len(t, res, 1)
		assert.True(t, res[0]["success"].(bool))
		assert.False(t, res[0]["created"].(bool))  // Should be update
		assert.Equal(t, id, res[0]["id"].(string)) // Same ID

		// Verify Data
		req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("/services/data/v63.0/sobjects/%s/%s", objName, id), nil)
		w = httptest.NewRecorder()
		r.ServeHTTP(w, req)
		var rec map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &rec)
		assert.Equal(t, "String Update", rec["Name"])
	})

	// =========================================================================
	// Scenario B: Upsert via Numeric External ID (Regression Verification)
	// =========================================================================
	t.Run("Numeric External ID", func(t *testing.T) {
		// Use a float/double number.
		// JSON numbers are float64 in Go.
		extNum := 12345.0 + float64(unix%1000)

		// Insert
		payload := map[string]interface{}{
			"allOrNone": true,
			"records": []map[string]interface{}{
				{"ExtNum__c": extNum, "Name": "Numeric Insert"},
			},
		}
		body, _ := json.Marshal(payload)
		// URL parameter is string, but payload has number.
		// Wait, endpoint /:sobject/:fieldName checks the field in BODY against fieldName in URL.
		// The URL doesn't contain the value.
		req, _ := http.NewRequest(http.MethodPatch, fmt.Sprintf("/services/data/v63.0/composite/sobjects/%s/ExtNum__c", objName), bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		// DEBUG: Print body if fail
		if w.Code != 200 {
			t.Logf("Upsert Numeric Failed: %s", w.Body.String())
		}

		assert.Equal(t, http.StatusOK, w.Code)
		var res []map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &res)
		require.Len(t, res, 1)

		if !res[0]["success"].(bool) {
			t.Fatalf("Numeric Insert Failed: %v", res[0]["errors"])
		}

		assert.True(t, res[0]["success"].(bool))
		assert.True(t, res[0]["created"].(bool))
		id := res[0]["id"].(string)

		// Update (same Numeric ID)
		payload["records"] = []map[string]interface{}{
			{"ExtNum__c": extNum, "Name": "Numeric Update"},
		}
		body, _ = json.Marshal(payload)
		req, _ = http.NewRequest(http.MethodPatch, fmt.Sprintf("/services/data/v63.0/composite/sobjects/%s/ExtNum__c", objName), bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		w = httptest.NewRecorder()
		r.ServeHTTP(w, req)

		assert.Equal(t, http.StatusOK, w.Code)
		json.Unmarshal(w.Body.Bytes(), &res)
		require.Len(t, res, 1)
		assert.True(t, res[0]["success"].(bool))
		assert.False(t, res[0]["created"].(bool)) // Should be update
		assert.Equal(t, id, res[0]["id"].(string))

		// Verify Data
		req, _ = http.NewRequest(http.MethodGet, fmt.Sprintf("/services/data/v63.0/sobjects/%s/%s", objName, id), nil)
		w = httptest.NewRecorder()
		r.ServeHTTP(w, req)
		var rec map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &rec)
		assert.Equal(t, "Numeric Update", rec["Name"])
	})

	// =========================================================================
	// Scenario C: Security - Invalid Object (Probe Prevention)
	// =========================================================================
	t.Run("Security: Invalid Object", func(t *testing.T) {
		payload := map[string]interface{}{
			"allOrNone": true,
			"records": []map[string]interface{}{
				{"Name": "Hack"},
			},
		}
		body, _ := json.Marshal(payload)
		// Upsert to NonExistentObject
		req, _ := http.NewRequest(http.MethodPatch, "/services/data/v63.0/composite/sobjects/NonExistentObject/Id", bytes.NewBuffer(body))
		req.Header.Set("Content-Type", "application/json")
		w := httptest.NewRecorder()
		r.ServeHTTP(w, req)

		// Expect 404 (Object not found) from schema pre-check
		assert.Equal(t, http.StatusNotFound, w.Code)
		var res []map[string]interface{}
		json.Unmarshal(w.Body.Bytes(), &res)
		// Check error code
		assert.Equal(t, "NOT_FOUND", res[0]["errorCode"])
	})
}
