package metadata

import (
	"context"
	"errors"
	"strings"
	"testing"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestValidateSupportedAtomicFlowAuthoring(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name        string
		definition  *models.FlowDefinition
		version     *models.FlowVersion
		wantErr     bool
		wantMessage string
	}{
		{
			name: "allows non-atomic flow",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					Start: &models.FlowStart{TriggerType: "RecordAfterSave"},
				},
			},
		},
		{
			name: "allows atomic before-save trigger flow",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Start:         &models.FlowStart{TriggerType: "RecordBeforeSave", Object: "Lead"},
				},
			},
		},
		{
			name: "allows atomic non-trigger flow",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Start:         &models.FlowStart{},
				},
			},
		},
		{
			name: "rejects circular formula resources before authoring",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					Formulas: []models.FlowFormula{
						{Name: "First", DataType: "Number", Expression: "Second + 1"},
						{Name: "Second", DataType: "Number", Expression: "First + 1"},
					},
				},
			},
			wantErr:     true,
			wantMessage: "circular",
		},
		{
			name: "rejects formula and variable namespace collision before authoring",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					Variables: []models.FlowVariable{{Name: "SharedName", DataType: "Number"}},
					Formulas:  []models.FlowFormula{{Name: "SharedName", DataType: "Number", Expression: "1"}},
				},
			},
			wantErr:     true,
			wantMessage: "not unique",
		},
		{
			name: "allows input-bound atomic parent prelock",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Variables:     []models.FlowVariable{{Name: "leadId", DataType: "String", IsInput: true}},
					AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
						Name:                 "LeadCampaigns",
						ParentObject:         "Campaign",
						SourceObject:         "CampaignMember",
						ParentReferenceField: "CampaignId",
						Filters: []models.FlowRecordFilter{{
							Field:    "LeadId",
							Operator: "EqualTo",
							Value:    map[string]interface{}{"elementReference": "leadId"},
						}},
					}},
				},
			},
		},
		{
			name: "rejects malformed atomic parent prelock operator",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Variables:     []models.FlowVariable{{Name: "leadId", DataType: "String", IsInput: true}},
					AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
						Name:                 "LeadCampaigns",
						ParentObject:         "Campaign",
						SourceObject:         "CampaignMember",
						ParentReferenceField: "CampaignId",
						Filters: []models.FlowRecordFilter{{
							Field:    "LeadId",
							Operator: "NotAQueryOperator",
							Value:    map[string]interface{}{"elementReference": "leadId"},
						}},
					}},
				},
			},
			wantErr:     true,
			wantMessage: "unsupported operator",
		},
		{
			name: "rejects post-start atomic parent prelock reference",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Formulas:      []models.FlowFormula{{Name: "ComputedLeadId", DataType: "String", Expression: "leadId"}},
					Variables:     []models.FlowVariable{{Name: "leadId", DataType: "String", IsInput: true}},
					AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
						Name:                 "LeadCampaigns",
						ParentObject:         "Campaign",
						SourceObject:         "CampaignMember",
						ParentReferenceField: "CampaignId",
						Filters: []models.FlowRecordFilter{{
							Field:    "LeadId",
							Operator: "EqualTo",
							Value:    map[string]interface{}{"elementReference": "ComputedLeadId"},
						}},
					}},
				},
			},
			wantErr:     true,
			wantMessage: "not available before the first Flow element",
		},
		{
			name: "rejects malformed atomic parent prelock filter logic",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Variables:     []models.FlowVariable{{Name: "leadId", DataType: "String", IsInput: true}},
					AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
						Name:                 "LeadCampaigns",
						ParentObject:         "Campaign",
						SourceObject:         "CampaignMember",
						ParentReferenceField: "CampaignId",
						FilterLogic:          "1 99",
						Filters: []models.FlowRecordFilter{
							{Field: "LeadId", Operator: "EqualTo", Value: map[string]interface{}{"elementReference": "leadId"}},
							{Field: "Status", Operator: "EqualTo", Value: map[string]interface{}{"stringValue": "Sent"}},
						},
					}},
				},
			},
			wantErr:     true,
			wantMessage: "invalid filterLogic",
		},
		{
			name: "rejects prior-record atomic parent prelock root",
			version: &models.FlowVersion{Metadata: models.FlowMetadata{
				RunAtomically: true,
				Start:         &models.FlowStart{Object: "Lead", TriggerType: "RecordBeforeSave"},
				AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
					Name: "PriorAccount", ParentObject: "Account", RecordIDs: []interface{}{
						map[string]interface{}{"elementReference": "$Record__Old.AccountId"},
					},
				}},
			}},
			wantErr:     true,
			wantMessage: "not available before the first Flow element",
		},
		{
			name: "rejects arbitrary map atomic parent prelock value",
			version: &models.FlowVersion{Metadata: models.FlowMetadata{
				RunAtomically: true,
				Variables:     []models.FlowVariable{{Name: "leadId", DataType: "String", IsInput: true}},
				AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
					Name:                 "NestedMap",
					ParentObject:         "Campaign",
					SourceObject:         "CampaignMember",
					ParentReferenceField: "CampaignId",
					Filters: []models.FlowRecordFilter{{
						Field: "LeadId", Operator: "EqualTo",
						Value: map[string]interface{}{"rhs": map[string]interface{}{"elementReference": "leadId"}},
					}},
				}},
			}},
			wantErr:     true,
			wantMessage: "supported typed literal wrapper or elementReference",
		},
		{
			name: "rejects nested map inside typed atomic parent prelock record id",
			version: &models.FlowVersion{Metadata: models.FlowMetadata{
				RunAtomically: true,
				AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
					Name: "NestedTypedRecordId", ParentObject: "Account",
					RecordIDs: []interface{}{map[string]interface{}{
						"stringValue": map[string]interface{}{"rhs": "account-1"},
					}},
				}},
			}},
			wantErr:     true,
			wantMessage: "stringValue must contain a scalar value",
		},
		{
			name: "rejects slice inside typed atomic parent prelock filter",
			version: &models.FlowVersion{Metadata: models.FlowMetadata{
				RunAtomically: true,
				AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
					Name:                 "NestedTypedFilter",
					ParentObject:         "Campaign",
					SourceObject:         "CampaignMember",
					ParentReferenceField: "CampaignId",
					Filters: []models.FlowRecordFilter{{
						Field: "LeadId", Operator: "EqualTo",
						Value: map[string]interface{}{"stringValue": []interface{}{"lead-1"}},
					}},
				}},
			}},
			wantErr:     true,
			wantMessage: "stringValue must contain a scalar value",
		},
		{
			name: "rejects legacy emailAlert actionCalls after WorkflowAlert cutover",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					ActionCalls: []models.FlowActionCall{{
						Name:       "Legacy_Email_Alert",
						ActionName: "Account.Legacy_Alert",
						ActionType: "emailAlert",
					}},
				},
			},
			wantErr:     true,
			wantMessage: "actionType=emailAlert",
		},
		{
			name: "rejects atomic after-save trigger flow",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Start:         &models.FlowStart{TriggerType: "RecordAfterSave", Object: "Lead"},
				},
			},
			wantErr:     true,
			wantMessage: `triggerType "RecordAfterSave" is unsupported`,
		},
		{
			name: "rejects atomic before-delete trigger flow",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Start:         &models.FlowStart{TriggerType: "RecordBeforeDelete", Object: "Lead"},
				},
			},
			wantErr:     true,
			wantMessage: `triggerType "RecordBeforeDelete" is unsupported`,
		},
		{
			name: "rejects atomic after-delete trigger flow",
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Start:         &models.FlowStart{TriggerType: "RecordAfterDelete", Object: "Lead"},
				},
			},
			wantErr:     true,
			wantMessage: `triggerType "RecordAfterDelete" is unsupported`,
		},
		{
			name: "rejects atomic workflow-derived after-save trigger flow",
			version: &models.FlowVersion{
				ProcessType: "Workflow",
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Variables: []models.FlowVariable{{
						Name:       "myVariable_current",
						DataType:   "SObject",
						ObjectType: "Lead",
					}},
				},
			},
			wantErr:     true,
			wantMessage: `triggerType "RecordAfterSave" is unsupported`,
		},
		{
			name:       "rejects atomic workflow-derived trigger when version processType is blank and definition owns workflow shape",
			definition: &models.FlowDefinition{ProcessType: "Workflow"},
			version: &models.FlowVersion{
				Metadata: models.FlowMetadata{
					RunAtomically: true,
					Variables: []models.FlowVariable{{
						Name:       "myVariable_current",
						DataType:   "SObject",
						ObjectType: "Lead",
					}},
				},
			},
			wantErr:     true,
			wantMessage: `triggerType "RecordAfterSave" is unsupported`,
		},
	}

	campaign := &models.ObjectMetadata{APIName: "Campaign"}
	lead := &models.ObjectMetadata{APIName: "Lead"}
	campaignMember := &models.ObjectMetadata{
		APIName: "CampaignMember",
		Fields: []models.FieldMetadata{
			{APIName: "CampaignId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Campaign"}},
			{APIName: "LeadId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Lead"}},
		},
	}
	snapshot := &metadataSnapshot{
		schemas: []*models.ObjectMetadata{campaign, campaignMember, lead},
		schemaMap: map[string]*models.ObjectMetadata{
			"campaign": campaign, "campaignmember": campaignMember, "lead": lead,
		},
		fieldMap: map[string][]models.FieldMetadata{"campaignmember": campaignMember.Fields},
	}
	ms := &MetadataService{}
	ms.cache.Store(snapshot)
	ms.coreCache.Store(snapshot)
	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			err := ms.validateSupportedAtomicFlowAuthoring(context.Background(), tc.version, tc.definition)
			if tc.wantErr {
				if err == nil {
					t.Fatal("validateSupportedAtomicFlowAuthoring() error = nil, want error")
				}
				// The engine returns a neutral *DomainWireError; assert the
				// semantic kind and offending field directly. The boundary dialect
				// maps WireInvalidField → v63 INVALID_FIELD (covered by the
				// dialect's wire_domain_mapping golden); the engine stays neutral.
				//
				// The reject table here only exercises the Metadata-field paths
				// (validateSupportedAtomicFlowAuthoring's NewInvalidFieldError(...,
				// "Metadata") arms). A future filterFormula reject case would carry
				// the "Metadata.Start.FilterFormula" field instead.
				var domainErr *apperrors.DomainWireError
				if !errors.As(err, &domainErr) {
					t.Fatalf("error type = %T, want *apperrors.DomainWireError", err)
				}
				if domainErr.Kind != apperrors.WireInvalidField {
					t.Fatalf("kind = %q, want %q", domainErr.Kind, apperrors.WireInvalidField)
				}
				if len(domainErr.Fields) != 1 || domainErr.Fields[0] != "Metadata" {
					t.Fatalf("fields = %v, want [Metadata]", domainErr.Fields)
				}
				if tc.wantMessage != "" && !strings.Contains(domainErr.Message, tc.wantMessage) {
					t.Fatalf("message = %q, want substring %q", domainErr.Message, tc.wantMessage)
				}
				return
			}
			if err != nil {
				t.Fatalf("validateSupportedAtomicFlowAuthoring() error = %v, want nil", err)
			}
		})
	}
}

func TestValidateSupportedAtomicFlowAuthoringRejectsBeforeSaveHistoryPrelocks(t *testing.T) {
	account := &models.ObjectMetadata{
		APIName: "Account",
		Fields:  []models.FieldMetadata{{ID: "fld-account-name", APIName: "Name"}},
	}
	snapshot := &metadataSnapshot{
		schemas:   []*models.ObjectMetadata{account},
		schemaMap: map[string]*models.ObjectMetadata{"account": account},
		fieldMap:  map[string][]models.FieldMetadata{"account": account.Fields},
	}
	ms := &MetadataService{}
	ms.cache.Store(snapshot)
	ms.coreCache.Store(snapshot)
	version := &models.FlowVersion{Metadata: models.FlowMetadata{
		RunAtomically: true,
		Start:         &models.FlowStart{Object: "Account", TriggerType: "RecordBeforeSave"},
		AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
			Name:         "AccountRows",
			ParentObject: "Account",
			RecordIDs:    []interface{}{map[string]interface{}{"stringValue": "account-1"}},
		}},
		AtomicHistoryPrelocks: []models.FlowAtomicHistoryPrelock{{Object: "Account", Fields: []string{"Name"}}},
	}}

	err := ms.validateSupportedAtomicFlowAuthoring(context.Background(), version, nil)
	if err == nil || !strings.Contains(err.Error(), "before-save") {
		t.Fatalf("validateSupportedAtomicFlowAuthoring() error = %v, want before-save history-prelock rejection", err)
	}
}

func TestValidateSupportedAtomicFlowAuthoringRejectsHistoryPrelockFieldWithoutStableID(t *testing.T) {
	account := &models.ObjectMetadata{
		APIName: "Account",
		Fields:  []models.FieldMetadata{{APIName: "Name"}},
	}
	snapshot := &metadataSnapshot{
		schemas:   []*models.ObjectMetadata{account},
		schemaMap: map[string]*models.ObjectMetadata{"account": account},
		fieldMap:  map[string][]models.FieldMetadata{"account": account.Fields},
	}
	ms := &MetadataService{}
	ms.cache.Store(snapshot)
	ms.coreCache.Store(snapshot)
	version := &models.FlowVersion{Metadata: models.FlowMetadata{
		RunAtomically: true,
		AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
			Name:         "AccountRows",
			ParentObject: "Account",
			RecordIDs:    []interface{}{map[string]interface{}{"stringValue": "account-1"}},
		}},
		AtomicHistoryPrelocks: []models.FlowAtomicHistoryPrelock{{Object: "Account", Fields: []string{"Name"}}},
	}}

	err := ms.validateSupportedAtomicFlowAuthoring(context.Background(), version, nil)
	if err == nil || !strings.Contains(err.Error(), "stable FieldId") {
		t.Fatalf("validateSupportedAtomicFlowAuthoring() error = %v, want stable FieldId rejection", err)
	}
}

func TestValidateSupportedAtomicFlowAuthoringRejectsMissingHistoryPrelockField(t *testing.T) {
	account := &models.ObjectMetadata{
		APIName: "Account",
		Fields:  []models.FieldMetadata{{ID: "fld-account-name", APIName: "Name"}},
	}
	snapshot := &metadataSnapshot{
		schemas:   []*models.ObjectMetadata{account},
		schemaMap: map[string]*models.ObjectMetadata{"account": account},
		fieldMap:  map[string][]models.FieldMetadata{"account": account.Fields},
	}
	ms := &MetadataService{}
	ms.cache.Store(snapshot)
	ms.coreCache.Store(snapshot)
	version := &models.FlowVersion{Metadata: models.FlowMetadata{
		RunAtomically: true,
		AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
			Name:         "AccountRows",
			ParentObject: "Account",
			RecordIDs:    []interface{}{map[string]interface{}{"stringValue": "account-1"}},
		}},
		AtomicHistoryPrelocks: []models.FlowAtomicHistoryPrelock{{Object: "Account", Fields: []string{"Missing__c"}}},
	}}

	err := ms.validateSupportedAtomicFlowAuthoring(context.Background(), version, nil)
	if err == nil || !strings.Contains(err.Error(), `field "Missing__c" does not exist on object "Account"`) {
		t.Fatalf("validateSupportedAtomicFlowAuthoring() error = %v, want missing history prelock field", err)
	}
}

func TestValidateSupportedAtomicFlowAuthoringRejectsMissingPrelockRecordIDField(t *testing.T) {
	lead := &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{{
			APIName:     "AccountId",
			Type:        constants.FieldTypeReference,
			ReferenceTo: []string{"Account"},
		}},
	}
	account := &models.ObjectMetadata{APIName: "Account"}
	snapshot := &metadataSnapshot{
		schemas:   []*models.ObjectMetadata{lead, account},
		schemaMap: map[string]*models.ObjectMetadata{"lead": lead, "account": account},
		fieldMap:  map[string][]models.FieldMetadata{"lead": lead.Fields},
	}
	ms := &MetadataService{}
	ms.cache.Store(snapshot)
	ms.coreCache.Store(snapshot)
	version := &models.FlowVersion{Metadata: models.FlowMetadata{
		RunAtomically: true,
		Start:         &models.FlowStart{Object: "Lead"},
		AtomicParentPrelocks: []models.FlowAtomicParentPrelock{{
			Name:         "ExistingAccount",
			ParentObject: "Account",
			RecordIDs: []interface{}{
				map[string]interface{}{"elementReference": "$Record.Accountd"},
			},
		}},
	}}

	err := ms.validateSupportedAtomicFlowAuthoring(context.Background(), version, nil)
	if err == nil || !strings.Contains(err.Error(), `field "Accountd" does not exist on object "Lead"`) {
		t.Fatalf("validateSupportedAtomicFlowAuthoring() error = %v, want missing $Record field", err)
	}
}
