package rest_api_test

// Docs:
// - Endpoint: https://developer.salesforce.com/docs/atlas.en-us.254.0.api_rest.meta/api_rest/resources_search.htm
// - Request: https://developer.salesforce.com/docs/atlas.en-us.254.0.api_rest.meta/api_rest/resources_search.htm
// - Response: https://developer.salesforce.com/docs/atlas.en-us.254.0.api_rest.meta/api_rest/resources_search.htm
// - Sub-schema: https://developer.salesforce.com/docs/atlas.en-us.254.0.api_rest.meta/api_rest/resources_search.htm
// - Errors: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_rest.htm

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"testing"

	"github.com/nexuscrm/tests/contract/internal/contracttest"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestGetSearch(t *testing.T) {
	contracttest.EnsureLocalRESTBaseline(t)

	testCases := []struct {
		name    string
		arrange func(t *testing.T) searchFixture
		act     func(t *testing.T, fixture searchFixture) searchCallResult
		assert  func(t *testing.T, fixture searchFixture, result searchCallResult)
	}{
		{
			name: "returns documented searchRecords for matching SOSL results",
			// Doc: https://developer.salesforce.com/docs/atlas.en-us.254.0.api_rest.meta/api_rest/resources_search.htm
			arrange: func(t *testing.T) searchFixture {
				return createSearchFixture(t)
			},
			act: func(t *testing.T, fixture searchFixture) searchCallResult {
				path := "/search?q=" + url.QueryEscape(fmt.Sprintf(
					"FIND {%s} IN ALL FIELDS RETURNING Contact(Id, FirstName, LastName)",
					fixture.term,
				))
				return executeSearchEventually(t, path, func(body searchResponse) bool {
					return hasSearchRecord(body, "Contact", fixture.contactID)
				})
			},
			assert: func(t *testing.T, fixture searchFixture, result searchCallResult) {
				require.Equal(t, http.StatusOK, result.StatusCode)
				require.NotEmpty(t, result.Body.SearchRecords)

				contact := findSearchRecord(t, result.Body, "Contact", fixture.contactID)
				assertSearchRecordAttributes(t, contact, "Contact", fixture.contactID)
				firstName, ok := contact["FirstName"].(string)
				require.True(t, ok)
				assert.Equal(t, fixture.contactFirstName, firstName)
				lastName, ok := contact["LastName"].(string)
				require.True(t, ok)
				assert.Equal(t, fixture.contactLastName, lastName)
			},
		},
		{
			name: "search root lists related subresources when q is omitted",
			// Doc: https://developer.salesforce.com/docs/atlas.en-us.254.0.api_rest.meta/api_rest/resources_search.htm
			arrange: func(t *testing.T) searchFixture {
				return searchFixture{path: "/search"}
			},
			act: func(t *testing.T, fixture searchFixture) searchCallResult {
				return doAuthenticatedSearchRequest(t, fixture.path)
			},
			assert: func(t *testing.T, fixture searchFixture, result searchCallResult) {
				require.Equal(t, http.StatusOK, result.StatusCode)

				var body map[string]string
				require.NoError(t, json.Unmarshal(result.Raw, &body))

				assert.Equal(t, "/services/data/v63.0/search/layout", body["layout"])
				assert.Equal(t, "/services/data/v63.0/search/scopeOrder", body["scopeOrder"])
				assert.Equal(t, "/services/data/v63.0/search/suggestions", body["suggestions"])
			},
		},
		{
			name: "non-SOSL q parameter is rejected",
			// Doc: https://developer.salesforce.com/docs/atlas.en-us.254.0.api_rest.meta/api_rest/resources_search.htm
			arrange: func(t *testing.T) searchFixture {
				return searchFixture{
					path: "/search?q=" + url.QueryEscape("SELECT Id FROM Account"),
				}
			},
			act: func(t *testing.T, fixture searchFixture) searchCallResult {
				return doAuthenticatedSearchRequest(t, fixture.path)
			},
			assert: func(t *testing.T, fixture searchFixture, result searchCallResult) {
				require.Equal(t, http.StatusBadRequest, result.StatusCode)
				assertSearchErrorCodeAndMessageContains(t, result, "MALFORMED_SEARCH", "enclosed in braces")
			},
		},
		{
			name: "missing bearer token is rejected",
			// Doc: https://developer.salesforce.com/docs/atlas.en-us.254.0.api_rest.meta/api_rest/resources_search.htm
			arrange: func(t *testing.T) searchFixture {
				return searchFixture{
					path: "/services/data/v63.0/search?q=" + url.QueryEscape("FIND {Account} RETURNING Account(Id)"),
				}
			},
			act: func(t *testing.T, fixture searchFixture) searchCallResult {
				resp := contracttest.DoAnonymousRequest(t, http.MethodGet, fixture.path, nil)
				return readSearchCallResult(t, resp)
			},
			assert: func(t *testing.T, fixture searchFixture, result searchCallResult) {
				assertSearchErrorCodeAndMessageContains(t, result, "INVALID_SESSION_ID", "")
				assert.Equal(t, http.StatusUnauthorized, result.StatusCode)
			},
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			fixture := tc.arrange(t)
			result := tc.act(t, fixture)
			tc.assert(t, fixture, result)
		})
	}
}
