package rest

import (
	"testing"

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

// ---------------------------------------------------------------------------
// isSortable
// ---------------------------------------------------------------------------

func TestIsSortable(t *testing.T) {
	tests := []struct {
		name     string
		sfType   string
		expected bool
	}{
		{"string is sortable", "string", true},
		{"id is sortable", "id", true},
		{"reference is sortable", "reference", true},
		{"double is sortable", "double", true},
		{"boolean is sortable", "boolean", true},
		{"date is sortable", "date", true},
		{"datetime is sortable", "datetime", true},
		{"int is sortable", "int", true},
		{"currency is sortable", "currency", true},
		{"picklist is sortable", "picklist", true},
		{"email is sortable", "email", true},
		{"phone is sortable", "phone", true},
		{"url is sortable", "url", true},

		{"textarea is not sortable", "textarea", false},
		{"base64 is not sortable", "base64", false},
		{"anyType is not sortable", "anyType", false},
		{"address is not sortable", "address", false},
		{"location is not sortable", "location", false},
		{"multipicklist is not sortable", "multipicklist", false},
		{"encryptedstring is not sortable", "encryptedstring", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, isSortable(tt.sfType))
		})
	}
}

// ---------------------------------------------------------------------------
// isAggregatable
// ---------------------------------------------------------------------------

func TestIsAggregatable(t *testing.T) {
	tests := []struct {
		name     string
		sfType   string
		expected bool
	}{
		{"string is aggregatable", "string", true},
		{"double is aggregatable", "double", true},
		{"reference is aggregatable", "reference", true},
		{"boolean is aggregatable", "boolean", true},
		{"int is aggregatable", "int", true},
		{"date is aggregatable", "date", true},
		{"currency is aggregatable", "currency", true},
		{"picklist is aggregatable", "picklist", true},

		{"datetime is aggregatable", "datetime", true},
		{"email is aggregatable", "email", true},
		{"phone is aggregatable", "phone", true},
		{"url is aggregatable", "url", true},

		{"textarea is not aggregatable", "textarea", false},
		{"base64 is not aggregatable", "base64", false},
		{"anyType is not aggregatable", "anyType", false},
		{"address is not aggregatable", "address", false},
		{"location is not aggregatable", "location", false},
		{"multipicklist is not aggregatable", "multipicklist", false},
		{"encryptedstring is not aggregatable", "encryptedstring", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, isAggregatable(tt.sfType))
		})
	}
}

// ---------------------------------------------------------------------------
// isGroupable
// ---------------------------------------------------------------------------

func TestIsGroupable(t *testing.T) {
	tests := []struct {
		name     string
		sfType   string
		expected bool
	}{
		{"string is groupable", "string", true},
		{"double is groupable", "double", true},
		{"reference is groupable", "reference", true},
		{"boolean is groupable", "boolean", true},
		{"int is groupable", "int", true},
		{"date is groupable", "date", true},
		{"currency is groupable", "currency", true},
		{"picklist is groupable", "picklist", true},

		{"email is groupable", "email", true},
		{"phone is groupable", "phone", true},
		{"url is groupable", "url", true},

		{"textarea is not groupable", "textarea", false},
		{"base64 is not groupable", "base64", false},
		{"anyType is not groupable", "anyType", false},
		{"address is not groupable", "address", false},
		{"location is not groupable", "location", false},
		{"multipicklist is not groupable", "multipicklist", false},
		{"encryptedstring is not groupable", "encryptedstring", false},
		{"datetime is not groupable", "datetime", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, isGroupable(tt.sfType))
		})
	}
}

// ---------------------------------------------------------------------------
// isFilterable
// ---------------------------------------------------------------------------

func TestIsFilterable(t *testing.T) {
	tests := []struct {
		name     string
		sfType   string
		expected bool
	}{
		{"string is filterable", "string", true},
		{"datetime is filterable", "datetime", true},
		{"email is filterable", "email", true},
		{"multipicklist is filterable", "multipicklist", true},
		{"textarea is filterable", "textarea", true},

		{"encryptedstring is not filterable", "encryptedstring", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, isFilterable(tt.sfType))
		})
	}
}

// ---------------------------------------------------------------------------
// Cross-property divergence: documents where Salesforce field properties differ
// ---------------------------------------------------------------------------

func TestFieldPropertyDivergence(t *testing.T) {
	// datetime: sortable=true, aggregatable=true, groupable=false
	// This is the key divergence — datetime can appear in aggregate queries
	// but cannot be used in GROUP BY.
	assert.True(t, isSortable("datetime"), "datetime must be sortable")
	assert.True(t, isAggregatable("datetime"), "datetime must be aggregatable")
	assert.False(t, isGroupable("datetime"), "datetime must not be groupable")

	// email/phone/url: sortable=true, aggregatable=true, groupable=true
	for _, typ := range []string{"email", "phone", "url"} {
		assert.True(t, isSortable(typ), typ+" must be sortable")
		assert.True(t, isAggregatable(typ), typ+" must be aggregatable")
		assert.True(t, isGroupable(typ), typ+" must be groupable")
	}

	// multipicklist: sortable=false, aggregatable=false, groupable=false, filterable=true
	assert.False(t, isSortable("multipicklist"), "multipicklist must not be sortable")
	assert.False(t, isAggregatable("multipicklist"), "multipicklist must not be aggregatable")
	assert.False(t, isGroupable("multipicklist"), "multipicklist must not be groupable")
	assert.True(t, isFilterable("multipicklist"), "multipicklist must be filterable")

	// encryptedstring: all false — the most restricted type
	assert.False(t, isSortable("encryptedstring"), "encryptedstring must not be sortable")
	assert.False(t, isAggregatable("encryptedstring"), "encryptedstring must not be aggregatable")
	assert.False(t, isGroupable("encryptedstring"), "encryptedstring must not be groupable")
	assert.False(t, isFilterable("encryptedstring"), "encryptedstring must not be filterable")
}

// ---------------------------------------------------------------------------
// isNillable
// ---------------------------------------------------------------------------

func TestIsNillable(t *testing.T) {
	tests := []struct {
		name     string
		sfType   string
		required bool
		expected bool
	}{
		// Boolean fields are never nillable
		{"boolean not required", "boolean", false, false},
		{"boolean required", "boolean", true, false},

		// Non-boolean: nillable when not required
		{"string not required", "string", false, true},
		{"string required", "string", true, false},
		{"double not required", "double", false, true},
		{"double required", "double", true, false},
		{"reference not required", "reference", false, true},
		{"reference required", "reference", true, false},
		{"date not required", "date", false, true},
		{"textarea not required", "textarea", false, true},
		{"textarea required", "textarea", true, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			assert.Equal(t, tt.expected, isNillable(tt.sfType, tt.required))
		})
	}
}
