package catalogreconcile

import (
	"context"
	"strings"
	"testing"

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

func TestDecorateEffectiveCompoundFieldsPublishesPlatformUserName(t *testing.T) {
	release := verifiedPlatformRelease(t, nil)
	objects := []*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.FieldUsername, Type: constants.FieldTypeString},
			},
		},
	}

	decorated, err := decorateEffectiveCompoundFields(context.Background(), objects, &release)
	if err != nil {
		t.Fatal(err)
	}
	name := models.FindField(decorated[0], constants.FieldName)
	if name == nil || !name.IsNameField || !name.IsCompound || !name.IsVirtual ||
		name.VirtualKind != constants.VirtualFieldCompound || name.Createable || name.Updateable ||
		len(name.ComposedFrom) != 2 || name.ComposedFrom[0] != constants.FieldFirstName ||
		name.ComposedFrom[1] != constants.FieldLastName {
		t.Fatalf("decorated User.Name = %#v", name)
	}
	if source := models.FindField(objects[0], constants.FieldName); source != nil {
		t.Fatalf("source metadata gained platform compound field: %#v", source)
	}
}

func TestDecorateEffectiveCompoundFieldsSkipsPlatformUserNameWithoutAllMembers(t *testing.T) {
	release := verifiedPlatformRelease(t, nil)
	objects := []*models.ObjectMetadata{{
		APIName: constants.TableUser,
		Fields: []models.FieldMetadata{
			{APIName: constants.FieldFirstName, Type: constants.FieldTypeString},
		},
	}}

	decorated, err := decorateEffectiveCompoundFields(context.Background(), objects, &release)
	if err != nil {
		t.Fatal(err)
	}
	if name := models.FindField(decorated[0], constants.FieldName); name != nil {
		t.Fatalf("incomplete User schema gained platform compound field: %#v", name)
	}
}

func TestDecorateEffectiveCompoundFieldsIncludesPlatformAndReleaseCompounds(t *testing.T) {
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind:        objectSpecReleaseComponentKind,
		businessKey: "objects",
		behavior: []byte(`[{"apiName":"Lead","fields":[` +
			`{"apiName":"FirstName","compoundFieldName":"Name"},` +
			`{"apiName":"LastName","isNameField":true,"compoundFieldName":"Name"}` +
			`]}]`),
	}})
	objects := []*models.ObjectMetadata{
		{APIName: constants.TableUser, Fields: []models.FieldMetadata{
			{APIName: constants.FieldFirstName, Type: constants.FieldTypeString},
			{APIName: constants.FieldLastName, Type: constants.FieldTypeString},
		}},
		{APIName: "Lead", Fields: []models.FieldMetadata{
			{APIName: "FirstName", Type: constants.FieldTypeString},
			{APIName: "LastName", Type: constants.FieldTypeString, IsNameField: true},
		}},
	}

	decorated, err := decorateEffectiveCompoundFields(context.Background(), objects, &release)
	if err != nil {
		t.Fatal(err)
	}
	if userName := models.FindField(decorated[0], constants.FieldName); userName == nil || !userName.IsCompound {
		t.Fatalf("platform User.Name = %#v", userName)
	}
	if leadName := models.FindField(decorated[1], constants.FieldName); leadName == nil ||
		!leadName.IsCompound || !leadName.IsVirtual || !leadName.IsNameField {
		t.Fatalf("release Lead.Name = %#v", leadName)
	}
}

func TestDecorateCompoundFieldsFromVerifiedObjectSpecRelease(t *testing.T) {
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind:        objectSpecReleaseComponentKind,
		businessKey: "objects",
		behavior: []byte(`[{"apiName":"Lead","fields":[` +
			`{"apiName":"Name","createable":false,"updateable":false},` +
			`{"apiName":"FirstName","compoundFieldName":"Name"},` +
			`{"apiName":"LastName","compoundFieldName":"Name"}` +
			`]}]`),
	}})
	objects := []*models.ObjectMetadata{{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{
				APIName:     "Name",
				Type:        constants.FieldTypeString,
				IsNameField: true,
				Createable:  true,
				Updateable:  true,
			},
			{APIName: "FirstName", Type: constants.FieldTypeString},
			{APIName: "LastName", Type: constants.FieldTypeString},
		},
	}}
	decorated, err := decorateCompoundFieldsFromRelease(objects, &release)
	if err != nil {
		t.Fatal(err)
	}
	name := models.FindField(decorated[0], "Name")
	if name == nil || !name.IsCompound || name.IsVirtual ||
		name.VirtualKind != "" ||
		name.Createable || name.Updateable ||
		len(name.ComposedFrom) != 2 ||
		name.ComposedFrom[0] != "FirstName" || name.ComposedFrom[1] != "LastName" {
		t.Fatalf("decorated Lead.Name = %#v", name)
	}
	if source := models.FindField(objects[0], "Name"); source.IsCompound || source.IsVirtual ||
		!source.Createable || !source.Updateable {
		t.Fatalf("source metadata was mutated: %#v", source)
	}
	persistable := models.PersistableFields(decorated[0])
	if field := models.FindField(&models.ObjectMetadata{Fields: persistable}, "Name"); field == nil {
		t.Fatalf("physical compound Lead.Name was removed from persistable fields: %#v", persistable)
	}
}

func TestDecorateCompoundFieldsPreservesExplicitParentNameSemantics(t *testing.T) {
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind:        objectSpecReleaseComponentKind,
		businessKey: "objects",
		behavior: []byte(`[{"apiName":"Quote","fields":[` +
			`{"apiName":"Name","isNameField":true,"isCompound":true,"composedFrom":["QuoteNumber"]},` +
			`{"apiName":"QuoteNumber"}` +
			`]}]`),
	}})
	objects := []*models.ObjectMetadata{{
		APIName: "Quote",
		Fields: []models.FieldMetadata{
			{APIName: "QuoteNumber", Type: constants.FieldTypeString},
		},
	}}
	decorated, err := decorateCompoundFieldsFromRelease(objects, &release)
	if err != nil {
		t.Fatal(err)
	}
	name := models.FindField(decorated[0], "Name")
	if name == nil || !name.IsNameField || !name.IsCompound || !name.IsVirtual ||
		name.VirtualKind != constants.VirtualFieldCompound ||
		name.Createable || name.Updateable ||
		len(name.ComposedFrom) != 1 || name.ComposedFrom[0] != "QuoteNumber" {
		t.Fatalf("synthesized Quote.Name = %#v", name)
	}
}

func TestDecorateCompoundFieldsSynthesizesReleaseOwnedVirtualParent(t *testing.T) {
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind:        objectSpecReleaseComponentKind,
		businessKey: "objects",
		behavior: []byte(`[{"apiName":"Contact","fields":[` +
			`{"apiName":"Salutation","type":"string","compoundFieldName":"Name"},` +
			`{"apiName":"FirstName","type":"string","compoundFieldName":"Name"},` +
			`{"apiName":"LastName","type":"string","isNameField":true,"compoundFieldName":"Name"}` +
			`]}]`),
	}})
	objects := []*models.ObjectMetadata{{
		APIName: "Contact",
		Fields: []models.FieldMetadata{
			{APIName: "Salutation", Type: constants.FieldTypeString},
			{APIName: "FirstName", Type: constants.FieldTypeString},
			{APIName: "LastName", Type: constants.FieldTypeString, IsNameField: true},
		},
	}}
	decorated, err := decorateCompoundFieldsFromRelease(objects, &release)
	if err != nil {
		t.Fatal(err)
	}
	name := models.FindField(decorated[0], "Name")
	if name == nil || name.Type != constants.FieldTypeString || !name.IsNameField ||
		!name.IsCompound || !name.IsVirtual || name.VirtualKind != constants.VirtualFieldCompound ||
		name.Createable || name.Updateable ||
		len(name.ComposedFrom) != 3 || name.ComposedFrom[0] != "Salutation" ||
		name.ComposedFrom[1] != "FirstName" || name.ComposedFrom[2] != "LastName" {
		t.Fatalf("synthesized Contact.Name = %#v", name)
	}
	if source := models.FindField(objects[0], "Name"); source != nil {
		t.Fatalf("source metadata gained a virtual parent: %#v", source)
	}
}

func TestDecorateCompoundFieldsFailsClosedOnMixedInstalledRevision(t *testing.T) {
	release := verifiedPlatformRelease(t, []platformReleaseComponentFixture{{
		kind:        objectSpecReleaseComponentKind,
		businessKey: "objects",
		behavior: []byte(`[{"apiName":"Lead","fields":[` +
			`{"apiName":"Name"},` +
			`{"apiName":"FirstName","compoundFieldName":"Name"},` +
			`{"apiName":"LastName","compoundFieldName":"Name"}` +
			`]}]`),
	}})
	objects := []*models.ObjectMetadata{{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "Name", Type: constants.FieldTypeString},
			{APIName: "FirstName", Type: constants.FieldTypeString},
		},
	}}
	_, err := decorateCompoundFieldsFromRelease(objects, &release)
	if err == nil || !strings.Contains(err.Error(), "LastName is absent") {
		t.Fatalf("mixed-revision error = %v", err)
	}
}

func TestCompoundDeclarationsRejectRecursiveGraphs(t *testing.T) {
	tests := []struct {
		name   string
		fields []objectSpecReleaseField
	}{
		{
			name: "direct self reference",
			fields: []objectSpecReleaseField{
				{APIName: "A", IsCompound: true, ComposedFrom: []string{"A"}},
			},
		},
		{
			name: "mutual reference with folded identity",
			fields: []objectSpecReleaseField{
				{APIName: "A", IsCompound: true, ComposedFrom: []string{"b"}},
				{APIName: "B", IsCompound: true, ComposedFrom: []string{"a"}},
			},
		},
		{
			name: "three parent cycle",
			fields: []objectSpecReleaseField{
				{APIName: "A", IsCompound: true, ComposedFrom: []string{"B"}},
				{APIName: "B", IsCompound: true, ComposedFrom: []string{"C"}},
				{APIName: "C", IsCompound: true, ComposedFrom: []string{"A"}},
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			_, err := compoundDeclarationsForObject(objectSpecReleaseObject{
				APIName: "Example", Fields: test.fields,
			})
			if err == nil || !strings.Contains(err.Error(), "recursive compound field graph") {
				t.Fatalf("recursive compound error = %v", err)
			}
		})
	}
}

func TestCompoundDeclarationsAcceptDirectedAcyclicGraph(t *testing.T) {
	declarations, err := compoundDeclarationsForObject(objectSpecReleaseObject{
		APIName: "Example",
		Fields: []objectSpecReleaseField{
			{APIName: "Display", IsCompound: true, ComposedFrom: []string{"Name"}},
			{APIName: "Name", IsCompound: true, ComposedFrom: []string{"First", "Last"}},
			{APIName: "First"},
			{APIName: "Last"},
		},
	})
	if err != nil {
		t.Fatal(err)
	}
	if len(declarations) != 2 {
		t.Fatalf("compound declaration count = %d, want 2", len(declarations))
	}
}
