package analytics

import (
	"testing"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/fieldprovenance"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestExpandCompoundReturnFieldsUsesCanonicalRuntimeDeclaration(t *testing.T) {
	previous := fieldprovenance.Default()
	defer fieldprovenance.SetDefault(previous)

	fieldprovenance.SetDefault(fieldprovenance.BuildClassifier(
		nil,
		[]fieldprovenance.ObjectDeclaration{{
			APIName: "PersonLike",
			Fields: []fieldprovenance.FieldDeclaration{
				{APIName: constants.FieldName, IsCompound: true, ComposedFrom: []string{"Prefix", "Given", "Family"}},
				{APIName: "Prefix"},
				{APIName: "Given"},
				{APIName: "Family"},
			},
		}},
		nil,
		nil,
	))

	schema := &models.ObjectMetadata{
		APIName: "PersonLike",
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldName},
			{APIName: "Prefix"},
			{APIName: "Given"},
			{APIName: "Family"},
		},
	}

	got := expandCompoundReturnFields(schema, []string{"Id", constants.FieldName})
	want := []string{"Id", constants.FieldName, "Prefix", "Given", "Family"}
	if !equalAnalyticsStringSlices(got, want) {
		t.Fatalf("expanded fields = %#v, want %#v", got, want)
	}
}

func TestExpandCompoundReturnFieldsDoesNotInferFromFieldNames(t *testing.T) {
	previous := fieldprovenance.Default()
	defer fieldprovenance.SetDefault(previous)

	fieldprovenance.SetDefault(fieldprovenance.NewClassifier(nil, nil, nil, nil))

	schema := &models.ObjectMetadata{
		APIName: "PersonLike",
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldName},
			{APIName: "FirstName"},
			{APIName: "LastName", IsNameField: true},
		},
	}

	got := expandCompoundReturnFields(schema, []string{constants.FieldName})
	want := []string{constants.FieldName}
	if !equalAnalyticsStringSlices(got, want) {
		t.Fatalf("expanded fields = %#v, want %#v", got, want)
	}
}

func TestBuildQueryFieldsExpandsVirtualCompoundWithoutSelectingParent(t *testing.T) {
	schema := userVirtualCompoundSchema()

	got := buildQueryFields(schema, models.QueryRequest{
		SelectFields: []string{constants.FieldName},
	}, []string{constants.FieldID, constants.FieldFirstName, constants.FieldLastName})

	want := []string{constants.FieldID, constants.FieldFirstName, constants.FieldLastName}
	if !equalAnalyticsStringSlices(got, want) {
		t.Fatalf("query fields = %#v, want %#v", got, want)
	}
}

func TestBuildQueryFieldsExpandsVisibleVirtualCompoundForImplicitSelection(t *testing.T) {
	schema := userVirtualCompoundSchema()

	got := buildQueryFields(schema, models.QueryRequest{},
		[]string{constants.FieldID, constants.FieldFirstName, constants.FieldLastName, constants.FieldName})

	want := []string{constants.FieldID, constants.FieldFirstName, constants.FieldLastName}
	if !equalAnalyticsStringSlices(got, want) {
		t.Fatalf("query fields = %#v, want %#v", got, want)
	}
}

func TestBuildQueryFieldsExplicitProjectionDoesNotReadImplicitVisibleFields(t *testing.T) {
	mode := "active"
	formula := "Quantity * UnitPrice"
	schema := &models.ObjectMetadata{
		APIName: "LineItemLike",
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString, IsSystem: true},
			{APIName: constants.FieldName, Type: constants.FieldTypeString, IsNameField: true},
			{APIName: "TotalPrice", Type: constants.FieldTypeFormula, FormulaMode: &mode, Formula: &formula},
			{APIName: "UnrelatedWideField__c", Type: constants.FieldTypeString},
		},
	}

	got := buildQueryFields(schema, models.QueryRequest{
		ObjectAPIName: schema.APIName,
		SelectFields:  []string{"TotalPrice"},
	}, []string{constants.FieldID, constants.FieldName, "TotalPrice", "UnrelatedWideField__c"})

	want := []string{constants.FieldID, "TotalPrice"}
	if !equalAnalyticsStringSlices(got, want) {
		t.Fatalf("query fields = %#v, want %#v", got, want)
	}
}

func TestProjectVirtualCompoundFieldsSynthesizesAndPrunesSupportFields(t *testing.T) {
	schema := userVirtualCompoundSchema()
	rows := []models.SObject{
		{constants.FieldID: "1", constants.FieldFirstName: " Ada ", constants.FieldLastName: " Lovelace "},
		{constants.FieldID: "2", constants.FieldFirstName: "Ada", constants.FieldLastName: ""},
		{constants.FieldID: "3", constants.FieldFirstName: nil, constants.FieldLastName: "Lovelace"},
		{constants.FieldID: "4", constants.FieldFirstName: " ", constants.FieldLastName: nil},
	}

	got := projectVirtualCompoundFields(rows, schema, []string{constants.FieldName})
	wantNames := []interface{}{"Ada Lovelace", "Ada", "Lovelace", nil}
	for i, want := range wantNames {
		if got[i][constants.FieldName] != want {
			t.Fatalf("row %d Name = %#v, want %#v", i, got[i][constants.FieldName], want)
		}
		if _, ok := got[i][constants.FieldFirstName]; ok {
			t.Fatalf("row %d retained support field FirstName", i)
		}
		if _, ok := got[i][constants.FieldLastName]; ok {
			t.Fatalf("row %d retained support field LastName", i)
		}
	}
}

func TestProjectVirtualCompoundFieldsKeepsExplicitSupportFields(t *testing.T) {
	schema := userVirtualCompoundSchema()
	rows := []models.SObject{{
		constants.FieldID:        "1",
		constants.FieldFirstName: "Ada",
		constants.FieldLastName:  "Lovelace",
	}}

	got := projectVirtualCompoundFields(rows, schema, []string{constants.FieldName, constants.FieldFirstName})
	if got[0][constants.FieldName] != "Ada Lovelace" {
		t.Fatalf("Name = %#v, want Ada Lovelace", got[0][constants.FieldName])
	}
	if got[0][constants.FieldFirstName] != "Ada" {
		t.Fatalf("FirstName was pruned despite explicit selection")
	}
	if _, ok := got[0][constants.FieldLastName]; ok {
		t.Fatal("LastName retained even though it was only a support field")
	}
}

func TestFilterReadableFieldListDropsDeniedExplicitSupportFields(t *testing.T) {
	schema := userVirtualCompoundSchema()
	readable := map[string]string{
		"id":   constants.FieldID,
		"name": constants.FieldName,
	}

	got := filterReadableFieldList(schema,
		[]string{constants.FieldID, constants.FieldName, constants.FieldFirstName},
		readable)
	want := []string{constants.FieldID, constants.FieldName}
	if !equalAnalyticsStringSlices(got, want) {
		t.Fatalf("readable fields = %#v, want %#v", got, want)
	}
}

func userVirtualCompoundSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName: constants.TableUser,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldID, Type: constants.FieldTypeString},
			{APIName: constants.FieldFirstName, Type: constants.FieldTypeString},
			{APIName: constants.FieldLastName, Type: constants.FieldTypeString},
			{
				APIName:      constants.FieldName,
				Type:         constants.FieldTypeFormula,
				IsSystem:     true,
				IsNameField:  true,
				IsVirtual:    true,
				VirtualKind:  constants.VirtualFieldCompound,
				IsCompound:   true,
				ComposedFrom: []string{constants.FieldFirstName, constants.FieldLastName},
			},
		},
	}
}

func equalAnalyticsStringSlices(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i := range a {
		if a[i] != b[i] {
			return false
		}
	}
	return true
}
