package automation

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

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

type selectedApprovalPersistenceStub struct {
	PersistenceProvider
	objectName   string
	recordID     string
	fields       []string
	result       models.SObject
	selectedErr  error
	calls        int
	rawRecords   map[string]models.SObject
	rawCalls     int
	previewCalls int
	previewModes []bool
	updates      []selectedApprovalUpdate
}

type selectedApprovalUpdate struct {
	objectName string
	recordID   string
	values     models.SObject
}

func (s *selectedApprovalPersistenceStub) ReadSelectedSystemRecord(_ context.Context, objectName, recordID string, fields []string) (models.SObject, error) {
	s.calls++
	s.objectName = objectName
	s.recordID = recordID
	s.fields = append([]string(nil), fields...)
	return s.result, s.selectedErr
}

func (s *selectedApprovalPersistenceStub) GetRaw(_ context.Context, objectName, recordID string) (models.SObject, error) {
	s.rawCalls++
	record := s.rawRecords[objectName+"/"+recordID]
	return cloneAutomationSObject(record), nil
}

func (s *selectedApprovalPersistenceStub) PreviewBeforeUpdate(ctx context.Context, objectName, recordID string, updates models.SObject, _ models.SObject, _ *models.UserSession) (*models.PreparedRecordUpdate, error) {
	s.previewCalls++
	s.previewModes = append(s.previewModes, isPreviewMode(ctx))
	record := cloneAutomationSObject(s.rawRecords[objectName+"/"+recordID])
	for field, value := range updates {
		record[field] = value
	}
	return &models.PreparedRecordUpdate{
		Record:           record,
		EffectiveUpdates: cloneAutomationSObject(updates),
	}, nil
}

func (s *selectedApprovalPersistenceStub) Update(_ context.Context, objectName, recordID string, updates models.SObject, _ *models.UserSession) error {
	s.updates = append(s.updates, selectedApprovalUpdate{
		objectName: objectName,
		recordID:   recordID,
		values:     cloneAutomationSObject(updates),
	})
	return nil
}

func TestReadApprovalSystemFieldsUsesOneExactRecordsRead(t *testing.T) {
	persistence := &selectedApprovalPersistenceStub{result: models.SObject{constants.FieldID: "rec-1", "ParentId": "parent-1"}}
	service := &ApprovalService{persistence: persistence}

	got, err := service.readApprovalSystemFields(context.Background(), "Generic", "rec-1", []string{"ParentId", "parentid", constants.FieldID})
	if err != nil {
		t.Fatalf("readApprovalSystemFields: %v", err)
	}
	if persistence.calls != 1 || persistence.objectName != "Generic" || persistence.recordID != "rec-1" {
		t.Fatalf("selected read = calls:%d object:%q id:%q", persistence.calls, persistence.objectName, persistence.recordID)
	}
	if want := []string{constants.FieldID, "ParentId"}; !reflect.DeepEqual(persistence.fields, want) {
		t.Fatalf("selected fields = %v, want %v", persistence.fields, want)
	}
	if !reflect.DeepEqual(got, persistence.result) {
		t.Fatalf("result = %#v, want %#v", got, persistence.result)
	}
}

func TestReadApprovalSystemFieldsFailsClosedWithoutRecordsOwnedReader(t *testing.T) {
	service := &ApprovalService{
		persistence: struct{ PersistenceProvider }{},
	}

	if _, err := service.readApprovalSystemFields(context.Background(), "Generic", "rec-1", []string{"Status"}); err == nil {
		t.Fatal("readApprovalSystemFields error = nil, want missing Records-owned reader to fail closed")
	}
}

func TestRuntimeApprovalPersistenceRouterForwardsSelectedReadWithoutRawFallback(t *testing.T) {
	base := &selectedApprovalPersistenceStub{result: models.SObject{constants.FieldID: "rec-1", "Status": "Open"}}
	router, err := NewRuntimeApprovalPersistenceRouter(base)
	if err != nil {
		t.Fatalf("NewRuntimeApprovalPersistenceRouter: %v", err)
	}
	got, err := router.ReadSelectedSystemRecord(context.Background(), "Generic", "rec-1", []string{"Status"})
	if err != nil {
		t.Fatalf("ReadSelectedSystemRecord: %v", err)
	}
	if base.calls != 1 || !reflect.DeepEqual(base.fields, []string{"Status"}) {
		t.Fatalf("forwarded read = calls:%d fields:%v", base.calls, base.fields)
	}
	if !reflect.DeepEqual(got, base.result) {
		t.Fatalf("result = %#v, want %#v", got, base.result)
	}
}

func TestReplayFieldUpdateUsesExactMultiPicklistReadWithoutRawFallback(t *testing.T) {
	persistence := &selectedApprovalPersistenceStub{result: models.SObject{
		constants.FieldID: "rec-1",
		"Tags":            "A;B",
	}}
	service := &ApprovalService{persistence: persistence}
	err := service.executeReplayFieldUpdateMutations(context.Background(), []ApprovalActionReplayFieldUpdate{{
		ActionName:         "replace tags",
		FieldAPIName:       "Tags",
		FieldValue:         "A;C",
		UpdateObjectName:   "Generic",
		UpdateRecordID:     "rec-1",
		UpdateFieldAPIName: "Tags",
	}}, nil)
	if err != nil {
		t.Fatalf("executeReplayFieldUpdateMutations: %v", err)
	}
	if want := []string{constants.FieldID, "Tags"}; !reflect.DeepEqual(persistence.fields, want) {
		t.Fatalf("selected replay fields = %v, want %v", persistence.fields, want)
	}
	if persistence.rawCalls != 0 {
		t.Fatalf("GetRaw calls = %d, want no broad replay fallback", persistence.rawCalls)
	}
	if len(persistence.updates) != 1 || persistence.updates[0].objectName != "Generic" ||
		persistence.updates[0].recordID != "rec-1" || persistence.updates[0].values["Tags"] != "A;C" {
		t.Fatalf("replay updates = %#v, want exact MultiPicklist target update", persistence.updates)
	}
}

func TestReplayFieldUpdateFailsClosedWhenRecordsRejectsSelectionWithoutRawFallback(t *testing.T) {
	readErr := errors.New("selected field is read-protected")
	persistence := &selectedApprovalPersistenceStub{
		selectedErr: readErr,
		rawRecords: map[string]models.SObject{
			"Generic/rec-1": {constants.FieldID: "rec-1", "ProtectedValue": "must-never-load"},
		},
	}
	service := &ApprovalService{persistence: persistence}
	err := service.executeReplayFieldUpdateMutations(context.Background(), []ApprovalActionReplayFieldUpdate{{
		ActionName:         "replace protected value",
		FieldAPIName:       "ProtectedValue",
		FieldValue:         "replacement",
		UpdateObjectName:   "Generic",
		UpdateRecordID:     "rec-1",
		UpdateFieldAPIName: "ProtectedValue",
	}}, nil)
	if err == nil || !strings.Contains(err.Error(), readErr.Error()) {
		t.Fatalf("executeReplayFieldUpdateMutations error = %v, want selected-read rejection", err)
	}
	if persistence.rawCalls != 0 {
		t.Fatalf("GetRaw calls = %d, want no security fallback", persistence.rawCalls)
	}
	if len(persistence.updates) != 0 {
		t.Fatalf("updates = %#v, want none after selected-read rejection", persistence.updates)
	}
}

func TestFieldUpdatePlanningStateReadsImmutableBaseOnceAndAdvancesCurrent(t *testing.T) {
	persistence := &selectedApprovalPersistenceStub{rawRecords: map[string]models.SObject{
		"Generic/rec-1": {constants.FieldID: "rec-1", "Status": "Draft", "ParentId": "parent-1"},
	}}
	service := &ApprovalService{persistence: persistence}
	state := newFieldUpdatePlanningState()

	current, err := service.loadFieldUpdatePlanningRecord(context.Background(), state, "Generic", "rec-1", true)
	if err != nil {
		t.Fatalf("first load: %v", err)
	}
	current["Status"] = "Submitted"
	again, err := service.loadFieldUpdatePlanningRecord(context.Background(), state, "Generic", "rec-1", true)
	if err != nil {
		t.Fatalf("second load: %v", err)
	}
	if persistence.rawCalls != 1 {
		t.Fatalf("GetRaw calls = %d, want exactly one base snapshot read", persistence.rawCalls)
	}
	if again["Status"] != "Submitted" {
		t.Fatalf("current Status = %v, want ordered shadow mutation", again["Status"])
	}
	if state.base["Generic/rec-1"]["Status"] != "Draft" {
		t.Fatalf("base Status = %v, want immutable stored value", state.base["Generic/rec-1"]["Status"])
	}
}

func TestPlanOrderedFieldUpdatesReadsOneBroadBaseForRepeatedSameRecordActions(t *testing.T) {
	persistence := &selectedApprovalPersistenceStub{rawRecords: map[string]models.SObject{
		"Generic/rec-1": {constants.FieldID: "rec-1", "Status": "Draft", "Stage": "New", "Flag": false},
	}}
	service := &ApprovalService{persistence: persistence}
	actions := []models.SObject{
		{constants.FieldID: "a-1", "Name": "status", "ActionType": constants.ProcessActionTypeFieldUpdate, "FieldApiName": "Status", "FieldValue": "Submitted"},
		{constants.FieldID: "a-2", "Name": "stage", "ActionType": constants.ProcessActionTypeFieldUpdate, "FieldApiName": "Stage", "FieldValue": "Review"},
		{constants.FieldID: "a-3", "Name": "flag", "ActionType": constants.ProcessActionTypeFieldUpdate, "FieldApiName": "Flag", "FieldValue": "true"},
	}

	planned, err := service.planOrderedFieldUpdatesFromActions(context.Background(), actions, "Generic", "rec-1", nil)
	if err != nil {
		t.Fatalf("planOrderedFieldUpdatesFromActions: %v", err)
	}
	if len(planned) != len(actions) || persistence.previewCalls != len(actions) {
		t.Fatalf("planned=%d preview calls=%d, want %d ordered actions", len(planned), persistence.previewCalls, len(actions))
	}
	for i, previewMode := range persistence.previewModes {
		if !previewMode {
			t.Fatalf("preview call %d did not carry the no-DML Flow preview marker", i+1)
		}
	}
	if persistence.rawCalls != 1 {
		t.Fatalf("GetRaw calls = %d, want one immutable broad base for the record", persistence.rawCalls)
	}
	last := planned[len(planned)-1].PreparedUpdate.Record
	if last["Status"] != "Submitted" || last["Stage"] != "Review" || last["Flag"] != "true" {
		t.Fatalf("last ordered planning snapshot = %#v, want all prior action effects", last)
	}
}

func TestApprovalLockRuleReadFieldsUsesExactStaticFieldsAndFallsBackForUnsafeShapes(t *testing.T) {
	staticRule := `Status == "Closed" || Amount > 10 || ISBLANK(Tags) || $Profile.Name == "Sales"`
	staticSchema := &models.ObjectMetadata{LockRule: &staticRule, Fields: []models.FieldMetadata{
		{APIName: "Status", Type: constants.FieldTypeString},
		{APIName: "Amount", Type: constants.FieldTypeCurrency},
		{APIName: "Tags", Type: constants.FieldTypeMultiPicklist},
	}}
	fields, bounded := approvalLockRuleReadFields(staticSchema)
	if !bounded || !reflect.DeepEqual(fields, []string{"Status", "Amount", "Tags"}) {
		t.Fatalf("static fields = %v, bounded=%v", fields, bounded)
	}

	unknownRule := `MissingField == "x"`
	if fields, bounded := approvalLockRuleReadFields(&models.ObjectMetadata{LockRule: &unknownRule}); bounded || fields != nil {
		t.Fatalf("unknown-field rule = %v, bounded=%v; want broad compatibility fallback", fields, bounded)
	}

	dynamicRule := `record[Selector] == "x"`
	if fields, bounded := approvalLockRuleReadFields(&models.ObjectMetadata{LockRule: &dynamicRule}); bounded || fields != nil {
		t.Fatalf("dynamic rule = %v, bounded=%v; want broad compatibility fallback", fields, bounded)
	}
}
