package automation

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/nexuscrm/backend/internal/bootstrap"
	"github.com/nexuscrm/backend/pkg/formula"
	"github.com/nexuscrm/shared/pkg/models"
)

// TestResolver_Apply_FillsAbsentKeys pins stage-3 semantics. When the pack
// has an ActionFieldDefault row for (convertLead, Opportunity, CloseDate)
// and the record has no CloseDate key, the resolver evaluates the formula
// and writes the result. This is the happy path the CloseDate seed depends
// on.
func TestResolver_Apply_FillsAbsentKeys(t *testing.T) {
	r := NewDefaultFieldDefaultResolver(formula.NewEngine())
	rows := []bootstrap.ActionFieldDefaultRow{
		{TargetObject: "Opportunity", FieldAPIName: "CloseDate", ValueFormula: "TODAY() + 30", Priority: 0},
	}
	ctx := WithActionFieldDefaults(context.Background(), rows)

	record := models.SObject{"Name": "Acme"}
	require.NoError(t, r.Apply(ctx, record, "Opportunity", map[string]interface{}{}))

	closeDate, ok := record["CloseDate"]
	require.True(t, ok, "stage 3 must have filled CloseDate")
	assert.NotEmpty(t, closeDate, "evaluated formula must produce a non-empty value")
}

// TestResolver_Apply_RespectsExistingKey pins the layer-1 / layer-2 > layer-3
// contract: once a caller or a flow InputAssignment has set the field, stage
// 3 must NOT overwrite it. The record carrying a pre-existing key wins even
// if the ActionFieldDefault row has a different value.
func TestResolver_Apply_RespectsExistingKey(t *testing.T) {
	r := NewDefaultFieldDefaultResolver(formula.NewEngine())
	rows := []bootstrap.ActionFieldDefaultRow{
		{TargetObject: "Opportunity", FieldAPIName: "StageName", ValueFormula: "'PackDefault'"},
	}
	ctx := WithActionFieldDefaults(context.Background(), rows)

	record := models.SObject{"StageName": "CallerWrote"}
	require.NoError(t, r.Apply(ctx, record, "Opportunity", map[string]interface{}{}))

	assert.Equal(t, "CallerWrote", record["StageName"],
		"stage 3 must not overwrite a pre-existing key (layers 1/2 win)")
}

// TestResolver_Apply_FiltersByTargetObject pins that rows scoped to one
// object never leak into another object's create. If a pack declares
// Opportunity defaults and the current create is a Contact, stage 3 is
// a no-op for that create.
func TestResolver_Apply_FiltersByTargetObject(t *testing.T) {
	r := NewDefaultFieldDefaultResolver(formula.NewEngine())
	rows := []bootstrap.ActionFieldDefaultRow{
		{TargetObject: "Opportunity", FieldAPIName: "StageName", ValueFormula: "'Prospecting'"},
	}
	ctx := WithActionFieldDefaults(context.Background(), rows)

	record := models.SObject{"LastName": "Doe"}
	require.NoError(t, r.Apply(ctx, record, "Contact", map[string]interface{}{}))

	_, leaked := record["StageName"]
	assert.False(t, leaked, "Opportunity-scoped default must not leak into Contact")
}

// TestResolver_Apply_PriorityWinsWithinPair pins that when two rows target
// the same (Object, Field), the first in the input slice wins. Callers are
// expected to pass rows pre-sorted by Priority DESC (GetActionFieldDefaults
// does this); the resolver enforces "first winning row claims the slot".
func TestResolver_Apply_PriorityWinsWithinPair(t *testing.T) {
	r := NewDefaultFieldDefaultResolver(formula.NewEngine())
	rows := []bootstrap.ActionFieldDefaultRow{
		{TargetObject: "Opportunity", FieldAPIName: "CloseDate", ValueFormula: "'2030-01-01'", Priority: 10},
		{TargetObject: "Opportunity", FieldAPIName: "CloseDate", ValueFormula: "'2020-01-01'", Priority: 0},
	}
	ctx := WithActionFieldDefaults(context.Background(), rows)

	record := models.SObject{}
	require.NoError(t, r.Apply(ctx, record, "Opportunity", map[string]interface{}{}))

	assert.Equal(t, "2030-01-01", record["CloseDate"],
		"higher-priority row (first in the slice) must win")
}

// TestResolver_Apply_ConditionGates pins the Condition evaluation step.
// A row whose Condition formula evaluates falsey must NOT fire — the
// unconditional row takes the slot instead.
func TestResolver_Apply_ConditionGates(t *testing.T) {
	r := NewDefaultFieldDefaultResolver(formula.NewEngine())
	rows := []bootstrap.ActionFieldDefaultRow{
		{TargetObject: "Opportunity", FieldAPIName: "StageName",
			ValueFormula: "'HighValue'", Condition: "Input.amount > 1000", Priority: 10},
		{TargetObject: "Opportunity", FieldAPIName: "StageName",
			ValueFormula: "'Standard'", Priority: 0},
	}
	ctx := WithActionFieldDefaults(context.Background(), rows)

	// amount = 100: condition is false, high-value row skips, standard row wins.
	record := models.SObject{}
	inputs := map[string]interface{}{"amount": 100}
	require.NoError(t, r.Apply(ctx, record, "Opportunity", inputs))
	assert.Equal(t, "Standard", record["StageName"])
}

// TestResolver_Apply_NoRowsOnCtx_NoOp pins that the resolver is inert when
// no ActionFieldDefault rows were stamped on ctx. Required so non-action
// flows (direct REST, tests, bootstrap) don't trip over stage 3 by accident.
func TestResolver_Apply_NoRowsOnCtx_NoOp(t *testing.T) {
	r := NewDefaultFieldDefaultResolver(formula.NewEngine())
	record := models.SObject{"Name": "Acme"}
	require.NoError(t, r.Apply(context.Background(), record, "Opportunity", map[string]interface{}{}))
	assert.Len(t, record, 1, "resolver must be a no-op without ctx-stamped rows")
}

