package rest

import (
	"testing"

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

// ---------------------------------------------------------------------------
// parseSOSL — SOSL query parser
// ---------------------------------------------------------------------------

func TestParseSOSL_SimpleFIND(t *testing.T) {
	term, scope, configs, err := parseSOSL("FIND {test}")
	require.NoError(t, err)
	assert.Equal(t, "test", term)
	assert.Equal(t, "ALL", scope)
	assert.Empty(t, configs, "No RETURNING clause means empty configs")
}

func TestParseSOSL_WithReturning(t *testing.T) {
	term, scope, configs, err := parseSOSL("FIND {acme} RETURNING Account")
	require.NoError(t, err)
	assert.Equal(t, "acme", term)
	assert.Equal(t, "ALL", scope, "Default scope should be ALL")
	require.Len(t, configs, 1)
	assert.Equal(t, "Account", configs[0].Name)
	assert.Equal(t, constants.DefaultSOSLLimit, configs[0].Limit)
}

func TestParseSOSL_MultipleObjects(t *testing.T) {
	term, scope, configs, err := parseSOSL("FIND {acme} RETURNING Account, Contact, Lead")
	require.NoError(t, err)
	assert.Equal(t, "acme", term)
	assert.Equal(t, "ALL", scope)
	require.Len(t, configs, 3)
	assert.Equal(t, "Account", configs[0].Name)
	assert.Equal(t, "Contact", configs[1].Name)
	assert.Equal(t, "Lead", configs[2].Name)
}

func TestParseSOSL_WithFieldList(t *testing.T) {
	term, _, configs, err := parseSOSL("FIND {acme} RETURNING Account(Id, Name)")
	require.NoError(t, err)
	assert.Equal(t, "acme", term)
	require.Len(t, configs, 1)
	assert.Equal(t, "Account", configs[0].Name)
	assert.Contains(t, configs[0].Fields, "Id")
	assert.Contains(t, configs[0].Fields, "Name")
}

func TestParseSOSL_WithLimit(t *testing.T) {
	_, _, configs, err := parseSOSL("FIND {acme} RETURNING Account(Id, Name LIMIT 10)")
	require.NoError(t, err)
	require.Len(t, configs, 1)
	assert.Equal(t, 10, configs[0].Limit)
}

func TestParseSOSL_WithOrderBy(t *testing.T) {
	_, _, configs, err := parseSOSL("FIND {acme} RETURNING Account(Id, Name ORDER BY Name DESC)")
	require.NoError(t, err)
	require.Len(t, configs, 1)
	assert.Equal(t, "Name", configs[0].OrderBy)
	assert.Equal(t, "DESC", configs[0].OrderDir)
}

func TestParseSOSL_WithOffset(t *testing.T) {
	_, _, configs, err := parseSOSL("FIND {acme} RETURNING Account(Id, Name LIMIT 10 OFFSET 20)")
	require.NoError(t, err)
	require.Len(t, configs, 1)
	assert.Equal(t, 10, configs[0].Limit)
	assert.Equal(t, 20, configs[0].Offset)
}

func TestParseSOSL_WithWhere(t *testing.T) {
	_, _, configs, err := parseSOSL("FIND {laptop} RETURNING Case(Id, Subject WHERE Status='New')")
	require.NoError(t, err)
	require.Len(t, configs, 1)
	assert.Equal(t, "Status='New'", configs[0].Filter)
}

func TestParseSOSL_InNameFields(t *testing.T) {
	_, scope, _, err := parseSOSL("FIND {test} IN NAME FIELDS")
	require.NoError(t, err)
	assert.Equal(t, "NAME", scope)
}

func TestParseSOSL_InEmailFields(t *testing.T) {
	_, scope, _, err := parseSOSL("FIND {test@example.com} IN EMAIL FIELDS")
	require.NoError(t, err)
	assert.Equal(t, "EMAIL", scope)
}

func TestParseSOSL_InPhoneFields(t *testing.T) {
	_, scope, _, err := parseSOSL("FIND {5551234} IN PHONE FIELDS")
	require.NoError(t, err)
	assert.Equal(t, "PHONE", scope)
}

func TestParseSOSL_InAllFields(t *testing.T) {
	_, scope, _, err := parseSOSL("FIND {test} IN ALL FIELDS")
	require.NoError(t, err)
	assert.Equal(t, "ALL", scope)
}

func TestParseSOSL_CaseInsensitive(t *testing.T) {
	term, scope, configs, err := parseSOSL("find {test} in all fields returning Account")
	require.NoError(t, err)
	assert.Equal(t, "test", term)
	assert.Equal(t, "ALL", scope)
	require.Len(t, configs, 1)
	assert.Equal(t, "Account", configs[0].Name)
}

func TestParseSOSL_NoFINDKeyword(t *testing.T) {
	_, _, _, err := parseSOSL("SELECT Name FROM Account")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "SOSL query must start with FIND")
}

func TestParseSOSL_MissingBraces(t *testing.T) {
	_, _, _, err := parseSOSL("FIND test")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "braces")
}

func TestParseSOSL_EmptyTerm(t *testing.T) {
	_, _, _, err := parseSOSL("FIND {}")
	assert.Error(t, err)
	assert.Contains(t, err.Error(), "empty")
}

func TestParseSOSL_SanitizesSpecialChars(t *testing.T) {
	term, _, _, err := parseSOSL("FIND {test'; DROP TABLE}")
	require.NoError(t, err)
	assert.NotContains(t, term, "'")
	assert.NotContains(t, term, ";")
}

func TestParseSOSL_WhitespaceInTerm(t *testing.T) {
	term, _, _, err := parseSOSL("FIND {  hello world  }")
	require.NoError(t, err)
	assert.Equal(t, "hello world", term)
}

func TestParseSOSL_ComplexQuery(t *testing.T) {
	query := "FIND {laptop} IN ALL FIELDS RETURNING Case(Id, Subject, Status WHERE Status='New' ORDER BY Subject ASC LIMIT 5 OFFSET 10)"
	term, scope, configs, err := parseSOSL(query)
	require.NoError(t, err)
	assert.Equal(t, "laptop", term)
	assert.Equal(t, "ALL", scope)
	require.Len(t, configs, 1)
	assert.Equal(t, "Case", configs[0].Name)
	assert.Equal(t, 5, configs[0].Limit)
	assert.Equal(t, 10, configs[0].Offset)
	assert.Equal(t, "Subject", configs[0].OrderBy)
	assert.Equal(t, "ASC", configs[0].OrderDir)
	assert.Equal(t, "Status='New'", configs[0].Filter)
}

func TestParseSOSL_MixedObjectsWithAndWithoutFields(t *testing.T) {
	query := "FIND {acme} RETURNING Account(Id, Name), Contact, Lead(Email LIMIT 3)"
	_, _, configs, err := parseSOSL(query)
	require.NoError(t, err)
	require.Len(t, configs, 3)
	assert.Equal(t, "Account", configs[0].Name)
	assert.Contains(t, configs[0].Fields, "Id")
	assert.Equal(t, "Contact", configs[1].Name)
	assert.Equal(t, "Lead", configs[2].Name)
	assert.Equal(t, 3, configs[2].Limit)
}

// ---------------------------------------------------------------------------
// parseFieldListWithOptions — field list parser
// ---------------------------------------------------------------------------

func TestParseFieldListWithOptions_SimpleFields(t *testing.T) {
	fields, limit, offset, orderBy, orderDir, filter := parseFieldListWithOptions("Id, Name, Email")
	assert.Equal(t, []string{"Id", "Name", "Email"}, fields)
	assert.Equal(t, constants.DefaultSOSLLimit, limit)
	assert.Equal(t, 0, offset)
	assert.Equal(t, "", orderBy)
	assert.Equal(t, "", orderDir)
	assert.Equal(t, "", filter)
}

func TestParseFieldListWithOptions_WithLimit(t *testing.T) {
	fields, limit, _, _, _, _ := parseFieldListWithOptions("Id, Name LIMIT 25")
	assert.Equal(t, []string{"Id", "Name"}, fields)
	assert.Equal(t, 25, limit)
}

func TestParseFieldListWithOptions_WithOrderBy(t *testing.T) {
	_, _, _, orderBy, orderDir, _ := parseFieldListWithOptions("Id, Name ORDER BY Name DESC")
	assert.Equal(t, "Name", orderBy)
	assert.Equal(t, "DESC", orderDir)
}

func TestParseFieldListWithOptions_WithWhere(t *testing.T) {
	fields, _, _, _, _, filter := parseFieldListWithOptions("Id, Name WHERE Status='Active'")
	assert.Equal(t, []string{"Id", "Name"}, fields)
	assert.Equal(t, "Status='Active'", filter)
}

func TestParseFieldListWithOptions_Empty(t *testing.T) {
	fields, limit, offset, orderBy, orderDir, filter := parseFieldListWithOptions("")
	assert.Empty(t, fields)
	assert.Equal(t, constants.DefaultSOSLLimit, limit)
	assert.Equal(t, 0, offset)
	assert.Equal(t, "", orderBy)
	assert.Equal(t, "", orderDir)
	assert.Equal(t, "", filter)
}

func TestParseFieldListWithOptions_AllOptions(t *testing.T) {
	content := "Id, Name WHERE Active=true ORDER BY Name ASC LIMIT 10 OFFSET 5"
	fields, limit, offset, orderBy, orderDir, filter := parseFieldListWithOptions(content)
	assert.Equal(t, []string{"Id", "Name"}, fields)
	assert.Equal(t, 10, limit)
	assert.Equal(t, 5, offset)
	assert.Equal(t, "Name", orderBy)
	assert.Equal(t, "ASC", orderDir)
	assert.Equal(t, "Active=true", filter)
}

func TestBuildSOSLSearchFields_AllScopeIncludesCompoundMembers(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "Name", IsNameField: true, Type: constants.FieldTypeString},
			{APIName: "FirstName", Type: constants.FieldTypeString},
			{APIName: "LastName", Type: constants.FieldTypeString},
		},
	}

	fields := buildSOSLSearchFields(schema, "ALL")

	assert.Contains(t, fields, "Name")
	assert.Contains(t, fields, "FirstName")
	assert.Contains(t, fields, "LastName")
}

func TestBuildSOSLSearchFields_NameScopeIncludesCompoundMembers(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "Name", IsNameField: true, Type: constants.FieldTypeString},
			{APIName: "FirstName", Type: constants.FieldTypeString},
			{APIName: "LastName", Type: constants.FieldTypeString},
		},
	}

	fields := buildSOSLSearchFields(schema, "NAME")

	assert.Contains(t, fields, "Name")
	assert.Contains(t, fields, "FirstName")
	assert.Contains(t, fields, "LastName")
}

// ---------------------------------------------------------------------------
// normalizeNameField — Name field normalization for search results
// ---------------------------------------------------------------------------

func TestNormalizeNameField_RecordAlreadyHasName(t *testing.T) {
	record := models.SObject{"Id": "001", "Name": "Acme Corp"}
	schema := &models.ObjectMetadata{
		Fields: []models.FieldMetadata{{APIName: "Name", IsNameField: true}},
	}
	normalizeNameField(record, schema)
	assert.Equal(t, "Acme Corp", record["Name"])
}

func TestNormalizeNameField_SingleNameField(t *testing.T) {
	record := models.SObject{"Id": "001", "Name__c": "Test"}
	schema := &models.ObjectMetadata{
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "Name__c", IsNameField: true},
		},
	}
	normalizeNameField(record, schema)
	assert.Equal(t, "Test", record["Name"])
}

func TestNormalizeNameField_FirstNameFieldMissing_FallsThrough(t *testing.T) {
	// Simulates User object: Email is first IsNameField in schema order,
	// but only Username (second IsNameField) is in the record.
	record := models.SObject{"Id": "user-1", "Username": "admin@test.com"}
	schema := &models.ObjectMetadata{
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "Email", IsNameField: true},
			{APIName: "Username", IsNameField: true},
		},
	}
	normalizeNameField(record, schema)
	assert.Equal(t, "admin@test.com", record["Name"], "Should use Username when Email is absent")
}

func TestNormalizeNameField_NoNameFieldInRecord_FallbackToId(t *testing.T) {
	record := models.SObject{"Id": "user-1"}
	schema := &models.ObjectMetadata{
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "Email", IsNameField: true},
			{APIName: "Username", IsNameField: true},
		},
	}
	normalizeNameField(record, schema)
	assert.Equal(t, "user-1", record["Name"], "Should fallback to Id")
}

func TestNormalizeNameField_NoNameFieldsDefined(t *testing.T) {
	record := models.SObject{"Id": "001"}
	schema := &models.ObjectMetadata{
		Fields: []models.FieldMetadata{{APIName: "Id"}, {APIName: "Status"}},
	}
	normalizeNameField(record, schema)
	assert.Equal(t, "001", record["Name"], "Should fallback to Id when no IsNameField")
}

func TestNormalizeNameField_Product2Style_NameField(t *testing.T) {
	// Simulates Product2 object where the field is named "Name" but IsNameField is not set
	record := models.SObject{"Id": "prd-1", "Name": "Test Product"}
	schema := &models.ObjectMetadata{
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "Name", IsNameField: false}, // Product2 Name field not marked as IsNameField
			{APIName: "ProductCode"},
		},
	}
	normalizeNameField(record, schema)
	assert.Equal(t, "Test Product", record["Name"], "Should use Name field even when IsNameField is false")
}

// ---------------------------------------------------------------------------
// addCompoundMembers — compound field resolution for search
// ---------------------------------------------------------------------------

func TestAddCompoundMembers_LeadIsNameFieldExpandsViaPackspec(t *testing.T) {
	// Lead's `Salutation`/`FirstName`/`LastName` all carry
	// `compoundFieldName: "Name"` in standard_objects_spec.json. When the
	// caller passes the IsNameField surface (`LastName` for Lead), the
	// expander resolves to the canonical compound name and pulls all three
	// members from packspec — preserving SSoT for compound shape (#1455
	// IANTHEREAL P0 review on PR #1963).
	schema := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "Salutation", Type: constants.FieldTypeString},
			{APIName: "FirstName", Type: constants.FieldTypeString},
			{APIName: "LastName", IsNameField: true, Type: constants.FieldTypeString},
		},
	}

	var collected []string
	addFunc := func(field string) {
		collected = append(collected, field)
	}

	addCompoundMembers(schema, "LastName", addFunc)

	expected := []string{"Salutation", "FirstName", "LastName"}
	assert.ElementsMatch(t, expected, collected, "Should expand Lead's IsNameField via packspec compound metadata")
}

func TestAddCompoundMembers_LeadNameExpandsViaPackspec(t *testing.T) {
	// Querying by the canonical compound name should resolve through the
	// same packspec path.
	schema := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "Salutation", Type: constants.FieldTypeString},
			{APIName: "FirstName", Type: constants.FieldTypeString},
			{APIName: "LastName", IsNameField: true, Type: constants.FieldTypeString},
		},
	}

	var collected []string
	addFunc := func(field string) {
		collected = append(collected, field)
	}

	addCompoundMembers(schema, "Name", addFunc)

	expected := []string{"Salutation", "FirstName", "LastName"}
	assert.ElementsMatch(t, expected, collected, "Should expand Lead's compound Name via packspec metadata")
}

func TestAddCompoundMembers_UnknownObjectDoesNotExpand(t *testing.T) {
	// Compound shape is metadata-driven (Ian's P0). Objects with no packspec
	// entry are not expanded via heuristics — the prior FirstName/LastName
	// fallback would have re-authored compound shape locally, violating R11.
	schema := &models.ObjectMetadata{
		APIName: "SomeUnknownObject",
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "FirstName", Type: constants.FieldTypeString},
			{APIName: "LastName", Type: constants.FieldTypeString},
		},
	}

	var collected []string
	addFunc := func(field string) {
		collected = append(collected, field)
	}

	addCompoundMembers(schema, "Name", addFunc)

	assert.Empty(t, collected, "Unknown objects must not get heuristic compound expansion; metadata is SSoT")
}

func TestAddCompoundMembers_IsNameFieldNotFoundDoesNotAddMembers(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "Id"},
			{APIName: "FirstName", Type: constants.FieldTypeString},
			{APIName: "LastName", IsNameField: false, Type: constants.FieldTypeString}, // Not IsNameField
		},
	}

	var collected []string
	addFunc := func(field string) {
		collected = append(collected, field)
	}

	// This should not add compound members since LastName is not marked as IsNameField
	addCompoundMembers(schema, "LastName", addFunc)

	// Should collect nothing because LastName is not IsNameField and not the special "Name" field
	assert.Empty(t, collected, "Should not add any fields when the field is not IsNameField and not 'Name'")
}
