package automation

import (
	"context"
	"fmt"
	"testing"

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

// fakeFieldMappingApplier captures the triple and copies a fixed mapping
// from source to target so tests can assert both that the applier was
// consulted with the right coordinates and that its output reaches the
// insert payload before InputAssignments override.
type fakeFieldMappingApplier struct {
	mock.Mock
	copy map[string]string // source key -> target key
}

func (f *fakeFieldMappingApplier) ApplyMappings(ctx context.Context, sourceObject, targetObject, actionType string, source, target map[string]interface{}) error {
	args := f.Called(ctx, sourceObject, targetObject, actionType, source, target)
	if err := args.Error(0); err != nil {
		return err
	}
	for srcField, tgtField := range f.copy {
		if v, ok := source[srcField]; ok {
			target[tgtField] = v
		}
	}
	return nil
}

func TestExecuteRecordCreate_FieldMappingPopulatesRecord(t *testing.T) {
	mockPersist := new(MockPersistence)
	applier := &fakeFieldMappingApplier{copy: map[string]string{
		"FirstName": "FirstName",
		"LastName":  "LastName",
		"Email":     "Email",
	}}
	e := &FlowExecutor{persistence: mockPersist, fieldMappings: applier}

	interview := &models.FlowInterview{
		ContextData: map[string]interface{}{
			"$lead": models.SObject{
				"FirstName": "Ada",
				"LastName":  "Lovelace",
				"Email":     "ada@example.com",
			},
		},
	}
	create := &models.FlowRecordCreate{
		Name:   "CreateContact",
		Object: "Contact",
		FieldMapping: &models.FlowFieldMappingRef{
			SourceObject:    "Lead",
			SourceReference: "$lead",
			TargetObject:    "Contact",
			ActionType:      "convert",
		},
		Connector: &models.FlowConnector{TargetReference: "Next"},
	}

	applier.On("ApplyMappings", mock.Anything, "Lead", "Contact", "convert", mock.Anything, mock.Anything).Return(nil)
	mockPersist.On("Insert", mock.Anything, "Contact", mock.MatchedBy(func(rec models.SObject) bool {
		return rec["FirstName"] == "Ada" && rec["LastName"] == "Lovelace" && rec["Email"] == "ada@example.com"
	}), mock.Anything).Return(models.SObject{"Id": "003-new"}, nil)

	next, err := e.executeRecordCreate(context.Background(), interview, create, nil)
	require.NoError(t, err)
	assert.Equal(t, "Next", next)
	applier.AssertExpectations(t)
	mockPersist.AssertExpectations(t)
}

func TestExecuteRecordCreate_InputAssignmentsOverrideMappedValues(t *testing.T) {
	mockPersist := new(MockPersistence)
	applier := &fakeFieldMappingApplier{copy: map[string]string{"LastName": "LastName"}}
	e := &FlowExecutor{persistence: mockPersist, fieldMappings: applier}

	interview := &models.FlowInterview{
		ContextData: map[string]interface{}{
			"$lead": models.SObject{"LastName": "FromMapping"},
		},
	}
	create := &models.FlowRecordCreate{
		Name:   "CreateContact",
		Object: "Contact",
		FieldMapping: &models.FlowFieldMappingRef{
			SourceObject:    "Lead",
			SourceReference: "$lead",
			TargetObject:    "Contact",
			ActionType:      "convert",
		},
		InputAssignments: []models.FlowInputAssignment{
			{Field: "LastName", Value: "FromInputAssignment"},
		},
		Connector: &models.FlowConnector{TargetReference: "Next"},
	}

	applier.On("ApplyMappings", mock.Anything, "Lead", "Contact", "convert", mock.Anything, mock.Anything).Return(nil)
	mockPersist.On("Insert", mock.Anything, "Contact", mock.MatchedBy(func(rec models.SObject) bool {
		return rec["LastName"] == "FromInputAssignment"
	}), mock.Anything).Return(models.SObject{"Id": "003-new"}, nil)

	_, err := e.executeRecordCreate(context.Background(), interview, create, nil)
	require.NoError(t, err)
	mockPersist.AssertExpectations(t)
}

func TestExecuteRecordCreate_NoFieldMappingRef_SkipsApplier(t *testing.T) {
	mockPersist := new(MockPersistence)
	applier := &fakeFieldMappingApplier{}
	e := &FlowExecutor{persistence: mockPersist, fieldMappings: applier}

	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	create := &models.FlowRecordCreate{
		Name:      "CreateContact",
		Object:    "Contact",
		Connector: &models.FlowConnector{TargetReference: "Next"},
	}

	mockPersist.On("Insert", mock.Anything, "Contact", mock.Anything, mock.Anything).Return(models.SObject{"Id": "003-new"}, nil)

	_, err := e.executeRecordCreate(context.Background(), interview, create, nil)
	require.NoError(t, err)
	applier.AssertNotCalled(t, "ApplyMappings", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything)
}

func TestExecuteRecordCreate_FieldMappingRefWithoutApplierFaults(t *testing.T) {
	// Wiring gap must not silently drop mappings — the executor must fault
	// so the authoring mistake surfaces at invocation time.
	mockPersist := new(MockPersistence)
	e := &FlowExecutor{persistence: mockPersist, fieldMappings: nil}

	interview := &models.FlowInterview{ContextData: map[string]interface{}{
		"$lead": models.SObject{"LastName": "x"},
	}}
	create := &models.FlowRecordCreate{
		Name:   "CreateContact",
		Object: "Contact",
		FieldMapping: &models.FlowFieldMappingRef{
			SourceObject:    "Lead",
			SourceReference: "$lead",
			ActionType:      "convert",
		},
	}

	_, err := e.executeRecordCreate(context.Background(), interview, create, nil)
	require.Error(t, err)
	assert.Contains(t, err.Error(), "FieldMappingApplier")
	mockPersist.AssertNotCalled(t, "Insert", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
}

func TestExecuteRecordCreate_FieldMappingMissingSourceFaultsToFaultConnector(t *testing.T) {
	mockPersist := new(MockPersistence)
	applier := &fakeFieldMappingApplier{}
	e := &FlowExecutor{persistence: mockPersist, fieldMappings: applier}

	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	create := &models.FlowRecordCreate{
		Name:   "CreateContact",
		Object: "Contact",
		FieldMapping: &models.FlowFieldMappingRef{
			SourceObject:    "Lead",
			SourceReference: "$lead",
			ActionType:      "convert",
		},
		FaultConnector: &models.FlowConnector{TargetReference: "Fault"},
	}

	next, err := e.executeRecordCreate(context.Background(), interview, create, nil)
	require.NoError(t, err)
	assert.Equal(t, "Fault", next)
	assert.Contains(t, fmt.Sprint(interview.ContextData["$Flow.FaultMessage"]), "$lead")
	mockPersist.AssertNotCalled(t, "Insert", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
}

func TestApplyRecordCreateFieldMapping_DefaultsTargetToCreateObject(t *testing.T) {
	applier := &fakeFieldMappingApplier{copy: map[string]string{"Company": "AccountName"}}
	e := &FlowExecutor{fieldMappings: applier}

	interview := &models.FlowInterview{ContextData: map[string]interface{}{
		"$lead": models.SObject{"Company": "Acme"},
	}}
	create := &models.FlowRecordCreate{
		Name:   "CreateAccount",
		Object: "Account",
		FieldMapping: &models.FlowFieldMappingRef{
			SourceObject:    "Lead",
			SourceReference: "$lead",
			ActionType:      "convert",
		},
	}
	target := models.SObject{}

	applier.On("ApplyMappings", mock.Anything, "Lead", "Account", "convert", mock.Anything, mock.Anything).Return(nil)

	require.NoError(t, e.applyRecordCreateFieldMapping(context.Background(), interview, create, target))
	assert.Equal(t, "Acme", target["AccountName"])
	applier.AssertExpectations(t)
}

func TestExecuteRecordUpdate_FieldMappingFillBlankOnlyUpdatesOnlyBlankTargetFields(t *testing.T) {
	mockPersist := new(MockPersistence)
	applier := &fakeFieldMappingApplier{copy: map[string]string{
		"Company": "Name",
		"Phone":   "Phone",
		"Website": "Website",
		"OwnerId": "OwnerId",
	}}
	e := &FlowExecutor{persistence: mockPersist, fieldMappings: applier}

	interview := &models.FlowInterview{
		ContextData: map[string]interface{}{
			"$lead": models.SObject{
				"Company": "Acme",
				"Phone":   "555-0100",
				"Website": "",
				"OwnerId": "lead-owner",
			},
			"$account": models.SObject{
				"Id":    "acct-001",
				"Name":  "",
				"Phone": "already-set",
			},
		},
	}
	update := &models.FlowRecordUpdate{
		Name:           "MergeAccount",
		Object:         "Account",
		InputReference: "$account",
		FieldMapping: &models.FlowFieldMappingRef{
			SourceObject:    "Lead",
			SourceReference: "$lead",
			TargetObject:    "Account",
			ActionType:      "convert",
			MergeMode:       "fillBlankOnly",
		},
		InputAssignments: []models.FlowInputAssignment{
			{Field: "OwnerId", Value: "explicit-owner"},
		},
		Connector: &models.FlowConnector{TargetReference: "Next"},
	}

	applier.On("ApplyMappings", mock.Anything, "Lead", "Account", "convert", mock.Anything, mock.Anything).Return(nil)
	mockPersist.On("Update", mock.Anything, "Account", "acct-001", mock.MatchedBy(func(rec models.SObject) bool {
		_, hasPhone := rec["Phone"]
		_, hasWebsite := rec["Website"]
		return rec["Name"] == "Acme" &&
			rec["OwnerId"] == "explicit-owner" &&
			!hasPhone &&
			!hasWebsite
	}), mock.Anything).Return(nil)

	next, err := e.executeRecordUpdate(context.Background(), interview, update, nil)
	require.NoError(t, err)
	assert.Equal(t, "Next", next)
	applier.AssertExpectations(t)
	mockPersist.AssertExpectations(t)
}

func TestExecuteRecordUpdate_FieldMappingAppliesToCollectionRecords(t *testing.T) {
	for _, tc := range []struct {
		name       string
		collection interface{}
	}{
		{
			name: "typed sobject collection",
			collection: []models.SObject{
				{"Id": "acct-001", "Name": "Old 1", "Phone": "111"},
				{"Id": "acct-002", "Name": "Old 2", "Phone": "222"},
			},
		},
		{
			name: "interface sobject collection",
			collection: []interface{}{
				models.SObject{"Id": "acct-001", "Name": "Old 1", "Phone": "111"},
				models.SObject{"Id": "acct-002", "Name": "Old 2", "Phone": "222"},
			},
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			mockPersist := new(MockPersistence)
			applier := &fakeFieldMappingApplier{copy: map[string]string{
				"Company": "Name",
			}}
			e := &FlowExecutor{persistence: mockPersist, fieldMappings: applier}

			interview := &models.FlowInterview{
				ContextData: map[string]interface{}{
					"$lead":   models.SObject{"Company": "Acme"},
					"targets": tc.collection,
				},
			}
			update := &models.FlowRecordUpdate{
				Name:           "MergeAccounts",
				Object:         "Account",
				InputReference: "targets",
				FieldMapping: &models.FlowFieldMappingRef{
					SourceObject:    "Lead",
					SourceReference: "$lead",
					TargetObject:    "Account",
					ActionType:      "convert",
				},
				InputAssignments: []models.FlowInputAssignment{
					{Field: "OwnerId", Value: "owner-001"},
				},
				Connector: &models.FlowConnector{TargetReference: "Next"},
			}

			applier.On("ApplyMappings", mock.Anything, "Lead", "Account", "convert", mock.Anything, mock.Anything).Return(nil)
			mockPersist.On("Update", mock.Anything, "Account", "acct-001", mock.MatchedBy(func(rec models.SObject) bool {
				return rec["Name"] == "Acme" && rec["OwnerId"] == "owner-001" && rec["Phone"] == "111"
			}), mock.Anything).Return(nil).Once()
			mockPersist.On("Update", mock.Anything, "Account", "acct-002", mock.MatchedBy(func(rec models.SObject) bool {
				return rec["Name"] == "Acme" && rec["OwnerId"] == "owner-001" && rec["Phone"] == "222"
			}), mock.Anything).Return(nil).Once()

			next, err := e.executeRecordUpdate(context.Background(), interview, update, nil)
			require.NoError(t, err)
			assert.Equal(t, "Next", next)
			applier.AssertExpectations(t)
			mockPersist.AssertExpectations(t)
		})
	}
}
