package formula

import (
	"testing"

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

// #1780: ISCHANGED({!$Record.<field>}) on CREATE where <field> is a valid schema
// field absent from the create payload must NOT raise "unknown name <field>" and
// fail closed — it must resolve to false (no prior to compare). Verifies the
// PrepareRecordForEvaluation null-fill (current dimension) AND the _prior_
// injection (prior dimension).
func mk1780Schema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName: "Lead",
		Fields: []models.FieldMetadata{
			{APIName: "Company", Type: models.FieldType("string")},
			{APIName: "Country", Type: models.FieldType("string")},
		},
	}
}

func TestNullFill1780_ISCHANGED_AbsentSchemaField_NoError(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record: map[string]interface{}{"Company": "Acme"}, // Country absent
		Prior:  nil,                                       // create
		Schema: mk1780Schema(),
	}
	result, err := engine.Evaluate(`ISCHANGED({!$Record.Country})`, ctx)
	if err != nil {
		t.Fatalf("#1780: ISCHANGED on absent schema field must not error (over-reject); got err=%v", err)
	}
	if result != false {
		t.Errorf("#1780: ISCHANGED(absent field) on create = %v; want false", result)
	}
}

func TestNullFill1780_TypoField_StillFailsClosed(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record: map[string]interface{}{"Company": "Acme"},
		Prior:  nil,
		Schema: mk1780Schema(),
	}
	// Nonexistent__c is NOT in schema → must still error (typo fail-closed, #637).
	_, err := engine.Evaluate(`ISCHANGED({!$Record.Nonexistent__c})`, ctx)
	if err == nil {
		t.Error("#1780/#637: ISCHANGED on a non-schema field must still error (typo must fail closed)")
	}
}

func TestNullFill1780_ImmutableInput(t *testing.T) {
	engine := NewEngine()
	rec := map[string]interface{}{"Company": "Acme"}
	ctx := &Context{Record: rec, Prior: nil, Schema: mk1780Schema()}
	_, _ = engine.Evaluate(`ISCHANGED({!$Record.Country})`, ctx)
	if _, leaked := rec["Country"]; leaked {
		t.Error("#1780: null-fill must not mutate the caller's record map (immutability)")
	}
}

// PRIORVALUE shares the same `_prior_<field>` lowering as ISCHANGED. An absent
// schema field must compile (not raise "unknown name _prior_<field>") and, per SF
// create semantics, resolve to its current value — which for an absent field is nil.
func TestNullFill1780_PRIORVALUE_AbsentSchemaField_NoError(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record: map[string]interface{}{"Company": "Acme"}, // Country absent
		Prior:  nil,                                       // create
		Schema: mk1780Schema(),
	}
	result, err := engine.Evaluate(`PRIORVALUE({!$Record.Country})`, ctx)
	if err != nil {
		t.Fatalf("#1780: PRIORVALUE on absent schema field must not error; got err=%v", err)
	}
	if result != nil {
		t.Errorf("#1780: PRIORVALUE(absent field) on create = %v; want nil (absent field's current value)", result)
	}
}

// IANTHEREAL P1 (#1785): SF PRIORVALUE(field) on CREATE returns the field's CURRENT
// value, NOT null, for a field PRESENT in the submitted record. Verified against SF
// help customize_functions_priorvalue + developer community ("on the creation of a
// new object, PRIORVALUE returns the current value of the new object").
func TestNullFill1780_PRIORVALUE_PresentField_OnCreate_ReturnsCurrent(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record: map[string]interface{}{"Company": "Prospect Co"},
		Prior:  nil, // create
		Schema: mk1780Schema(),
	}
	result, err := engine.Evaluate(`PRIORVALUE({!$Record.Company})`, ctx)
	if err != nil {
		t.Fatalf("#1785: PRIORVALUE on present field at create must not error; got err=%v", err)
	}
	if result != "Prospect Co" {
		t.Errorf("#1785: PRIORVALUE(present field) on create = %v; want current value \"Prospect Co\" (SF parity)", result)
	}
}

// Seeding _prior_ with the CURRENT value (for PRIORVALUE parity) must NOT flip
// ISCHANGED to true on create — ISCHANGED is guarded by _has_prior_=false.
func TestNullFill1780_ISCHANGED_PresentField_OnCreate_False(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record: map[string]interface{}{"Company": "Prospect Co"},
		Prior:  nil, // create
		Schema: mk1780Schema(),
	}
	changed, err := engine.Evaluate(`ISCHANGED({!$Record.Company})`, ctx)
	if err != nil {
		t.Fatalf("#1785: ISCHANGED on present field at create must not error; got err=%v", err)
	}
	if changed != false {
		t.Errorf("#1785: ISCHANGED(present field) on create = %v; want false (no prior on create)", changed)
	}
}

// The create-path null-fill (else branch, prior==nil) must NOT change the update
// path (if branch, prior!=nil): ISCHANGED still compares current vs prior.
func TestNullFill1780_Update_ISCHANGED_Unaffected(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record: map[string]interface{}{"Company": "New", "Country": "US"},
		Prior:  map[string]interface{}{"Company": "Old", "Country": "US"},
		Schema: mk1780Schema(),
	}
	changed, err := engine.Evaluate(`ISCHANGED({!$Record.Company})`, ctx)
	if err != nil || changed != true {
		t.Errorf("#1780: update ISCHANGED(changed field) = %v err=%v; want true", changed, err)
	}
	unchanged, err := engine.Evaluate(`ISCHANGED({!$Record.Country})`, ctx)
	if err != nil || unchanged != false {
		t.Errorf("#1780: update ISCHANGED(unchanged field) = %v err=%v; want false", unchanged, err)
	}
}

// Challenge-1 (challenger MED): update where PRIOR omits a field but CURRENT has it.
// prior null-fill makes _prior_<field>=nil; ISCHANGED must report true (value went
// from absent/NULL to set — correct Salesforce semantics, not a regression vs the
// pre-fix fail-closed).
func TestNullFill1780_Update_PriorMissingField_ISCHANGED(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record: map[string]interface{}{"Company": "Acme", "Country": "US"},
		Prior:  map[string]interface{}{"Company": "Acme"}, // Country absent in prior
		Schema: mk1780Schema(),
	}
	changed, err := engine.Evaluate(`ISCHANGED({!$Record.Country})`, ctx)
	if err != nil {
		t.Fatalf("#1780: update ISCHANGED with prior-missing field must not error; got %v", err)
	}
	if changed != true {
		t.Errorf("#1780: ISCHANGED(field absent in prior, set in current) = %v; want true", changed)
	}
}

// Challenge-2 (challenger HIGH, REFUTED): ISBLANK/ISNULL go through member access
// (not the bare-identifier ISCHANGED/PRIORVALUE lowering), so an absent field already
// resolved to nil BEFORE this fix (no compile error). null-fill keeps the same result —
// no behavior change for the 6 non-entry-gate consumers (sharing rules, validation, …).
func TestNullFill1780_ISBLANK_AbsentField_StillTrue(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record: map[string]interface{}{"Company": "Acme"}, // Country absent
		Prior:  nil,
		Schema: mk1780Schema(),
	}
	blank, err := engine.Evaluate(`ISBLANK({!$Record.Country})`, ctx)
	if err != nil {
		t.Fatalf("#1780: ISBLANK(absent field) must not error; got %v", err)
	}
	if blank != true {
		t.Errorf("#1780: ISBLANK(absent field) = %v; want true (member-access nil semantics, unchanged by null-fill)", blank)
	}
}

// Codex HIGH 1 (#1785): scheduled / non-record-trigger no-prior gates (ForceNotNew=true)
// must keep PRIORVALUE==nil — current-value seeding is record-create only.
func TestNullFill1785_PRIORVALUE_Scheduled_StaysNil(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record:      map[string]interface{}{"Company": "Acme"},
		Prior:       nil,
		Schema:      mk1780Schema(),
		ForceNotNew: true, // scheduled / non-record-trigger no-prior
	}
	result, err := engine.Evaluate(`PRIORVALUE({!$Record.Company})`, ctx)
	if err != nil {
		t.Fatalf("#1785: PRIORVALUE on scheduled no-prior must not error; got %v", err)
	}
	if result != nil {
		t.Errorf("#1785 Codex HIGH 1: PRIORVALUE on scheduled (ForceNotNew) = %v; want nil", result)
	}
}

// Codex HIGH 2 (#1785): PRIORVALUE must not leak an FLS-hidden field's current value
// on the no-prior path. Hidden fields (IsVisible=false) keep _prior_=nil; visible
// fields still get the record-create current value.
func TestNullFill1785_PRIORVALUE_FLSHidden_StaysNil(t *testing.T) {
	engine := NewEngine()
	ctx := &Context{
		Record: map[string]interface{}{"Company": "Acme", "Country": "Secret"},
		Prior:  nil, // record-create
		Schema: mk1780Schema(),
		IsVisible: func(field string) (bool, error) {
			return field != "Country", nil // Country is FLS-hidden
		},
	}
	vis, err := engine.Evaluate(`PRIORVALUE({!$Record.Company})`, ctx)
	if err != nil {
		t.Fatalf("#1785: PRIORVALUE(visible) must not error; got %v", err)
	}
	if vis != "Acme" {
		t.Errorf("#1785: PRIORVALUE(visible field) on create = %v; want \"Acme\" (record-create parity)", vis)
	}
	hid, err := engine.Evaluate(`PRIORVALUE({!$Record.Country})`, ctx)
	if err != nil {
		t.Fatalf("#1785: PRIORVALUE(hidden) must not error; got %v", err)
	}
	if hid != nil {
		t.Errorf("#1785 Codex HIGH 2 (FLS leak): PRIORVALUE(hidden field) = %v; want nil (no FLS side channel)", hid)
	}
}
