package integration

import (
	"encoding/json"
	"net/http"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// SalesforceError represents the standard error structure
type SalesforceError struct {
	ErrorCode string   `json:"errorCode"`
	Message   string   `json:"message"`
	Fields    []string `json:"fields,omitempty"`
}

func TestGoSF_NegativeValidation(t *testing.T) {
	if testing.Short() {
		t.Skip("skipping integration test in short mode")
	}

	// 1. Initialize Client (Use official SDK)
	sf := newGoSFClient(t)
	require.NotNil(t, sf, "SDK Client should initialize successfully")

	// Helper to decode error response from the Go error string
	decodeErrorsFromErr := func(t *testing.T, err error) []SalesforceError {
		require.Error(t, err, "Expected an error from SDK DoRequest")
		body := []byte(err.Error())
		var errs []SalesforceError
		// The SDK returns the body string as the error message.
		jsonErr := json.Unmarshal(body, &errs)
		require.NoError(t, jsonErr, "Should be able to unmarshal error array from error string: %s", string(body))
		return errs
	}

	t.Run("Create_UnknownField_ShouldFail", func(t *testing.T) {
		payload := map[string]interface{}{
			"Name":            "Invalid Field Test",
			"UnknownField__c": "Should fail",
		}

		payloadBytes, _ := json.Marshal(payload)
		resp, err := sf.DoRequest("POST", "/sobjects/Account", payloadBytes)

		// Expect err to be non-nil due to 400 Bad Request
		// SDK DoRequest returns nil response on error, so we cannot check generic StatusCode
		require.Error(t, err)
		require.Nil(t, resp, "Response should be nil on error from public SDK DoRequest")

		errs := decodeErrorsFromErr(t, err)
		require.Len(t, errs, 1)
		assert.Equal(t, "INVALID_FIELD", errs[0].ErrorCode)
	})

	t.Run("Create_SystemField_ShouldFail", func(t *testing.T) {
		payload := map[string]interface{}{
			"Name":        "System Field Test",
			"CreatedDate": "2023-01-01T00:00:00Z",
		}
		payloadBytes, _ := json.Marshal(payload)

		resp, err := sf.DoRequest("POST", "/sobjects/Account", payloadBytes)
		require.Error(t, err)
		require.Nil(t, resp)

		errs := decodeErrorsFromErr(t, err)
		require.Len(t, errs, 1)
		assert.Equal(t, "INVALID_FIELD", errs[0].ErrorCode)
		assert.Contains(t, errs[0].Message, "read-only system field")
	})

	t.Run("Get_NonExistentObject_ShouldReturn404", func(t *testing.T) {
		resp, err := sf.DoRequest("GET", "/sobjects/NonExistentObject__c/describe", nil)
		require.Error(t, err)
		require.Nil(t, resp)

		errs := decodeErrorsFromErr(t, err)
		require.Len(t, errs, 1)
		assert.Equal(t, "NOT_FOUND", errs[0].ErrorCode)
	})

	t.Run("Get_NonExistentRecord_ShouldReturn404", func(t *testing.T) {
		fakeID := "001000000000000FAKE"
		resp, err := sf.DoRequest("GET", "/sobjects/Account/"+fakeID, nil)
		require.Error(t, err)
		require.Nil(t, resp)

		errs := decodeErrorsFromErr(t, err)
		require.Len(t, errs, 1)
		assert.Equal(t, "NOT_FOUND", errs[0].ErrorCode)
	})

	t.Run("Concurrent_Create_SameUniqueField_ShouldHandleGracefully", func(t *testing.T) {
		uniqueName := "UniqueCo_" + time.Now().Format("20060102150405")

		errChan := make(chan error, 5)
		for i := 0; i < 5; i++ {
			go func() {
				payload := map[string]interface{}{
					"Name": uniqueName,
				}
				payloadBytes, _ := json.Marshal(payload)
				localSF := newGoSFClient(t)
				resp, err := localSF.DoRequest("POST", "/sobjects/Account", payloadBytes)
				if err != nil {
					errChan <- err
					return
				}
				defer resp.Body.Close()

				if resp.StatusCode != http.StatusCreated {
					errChan <- loggerErrorf("Status %d", resp.StatusCode)
				} else {
					errChan <- nil
				}
			}()
		}

		for i := 0; i < 5; i++ {
			err := <-errChan
			assert.NoError(t, err)
		}
	})

	t.Run("Concurrent_Update_SameRecord_ShouldNotDeadlock", func(t *testing.T) {
		// Create a record first
		payload := map[string]interface{}{"Name": "Concurrency Target"}
		// Use InsertOne for setup convenience
		res, err := sf.InsertOne("Account", payload)
		require.NoError(t, err)
		require.NotEmpty(t, res.Id)

		// 5 concurrent updates
		errChan := make(chan error, 5)
		for i := 0; i < 5; i++ {
			go func(idx int) {
				localSF := newGoSFClient(t)
				updatePayload := map[string]interface{}{
					"Name": "Updated Name", // Same value is fine, we just want to exercise the row lock
				}
				payloadBytes, _ := json.Marshal(updatePayload)

				resp, err := localSF.DoRequest("PATCH", "/sobjects/Account/"+res.Id, payloadBytes)
				if err != nil {
					errChan <- err
					return
				}
				defer resp.Body.Close()

				if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
					// 200 or 204
					errChan <- loggerErrorf("Status %d", resp.StatusCode)
				} else {
					errChan <- nil
				}
			}(i)
		}

		for i := 0; i < 5; i++ {
			err := <-errChan
			assert.NoError(t, err)
		}
	})
}

func loggerErrorf(format string, args ...interface{}) error {
	return nil
}
