package automation

import (
	"context"
	"testing"

	"github.com/nexuscrm/backend/internal/features/audit"
	"github.com/nexuscrm/backend/internal/features/events"
	"github.com/nexuscrm/shared/pkg/api"
	sharedEvents "github.com/nexuscrm/shared/pkg/events"
	"github.com/nexuscrm/shared/pkg/models"
)

// ctxCapturingQuery is a minimal ports.QueryService stub whose sole job is to
// record the ctx passed to Query(). Used by the propagation tests below to
// assert FlowExecutor.ExecuteFlow / ResumeInterview stamp
// audit.EntrypointFlow on ctx before invoking downstream helpers.
//
// Everything else returns the zero result, which makes the flow executor
// short-circuit on "flow not found" after its first Query — enough to observe
// the ctx at the entry boundary without wiring a full persistence stack.
type ctxCapturingQuery struct {
	capturedCtx context.Context
}

func (c *ctxCapturingQuery) Query(ctx context.Context, _ api.QueryRequest, _ *models.UserSession) (*models.QueryResult, error) {
	c.capturedCtx = ctx
	return &models.QueryResult{TotalSize: 0, Records: nil}, nil
}

func (c *ctxCapturingQuery) QueryWithFilter(ctx context.Context, _ string, _ string, _ *models.UserSession, _ string, _ string, _ int) (*models.QueryResult, error) {
	c.capturedCtx = ctx
	return &models.QueryResult{TotalSize: 0, Records: nil}, nil
}

func (c *ctxCapturingQuery) RunAnalytics(context.Context, models.AnalyticsQuery, *models.UserSession) (interface{}, error) {
	return nil, nil
}

func (c *ctxCapturingQuery) SearchSingleObject(context.Context, string, string, []string, []string, int, int, string, string, string, *models.UserSession) ([]models.SObject, error) {
	return nil, nil
}

func (c *ctxCapturingQuery) GlobalSearch(context.Context, string, *models.UserSession) ([]models.SearchResult, error) {
	return nil, nil
}

func (c *ctxCapturingQuery) Calculate(context.Context, string, models.SObject, *models.UserSession) (models.SObject, error) {
	return nil, nil
}

func (c *ctxCapturingQuery) ExecuteRawSQL(context.Context, string, []interface{}, *models.UserSession) ([]models.SObject, error) {
	return nil, nil
}

// TestExecuteFlow_StampsEntrypointFlowOnCtx asserts the C9 integration
// contract (design §3.5): when FlowExecutor.ExecuteFlow runs, downstream
// helpers observe audit.EntrypointFlow on ctx even if the caller entered
// with a bare context.Background().
func TestExecuteFlow_StampsEntrypointFlowOnCtx(t *testing.T) {
	q := &ctxCapturingQuery{}
	executor := &FlowExecutor{query: q}
	user := &models.UserSession{ID: "user-1"}

	// Flow lookup will short-circuit on TotalSize=0 — we only need the first
	// Query() to capture the ctx at the entry boundary.
	_, err := executor.ExecuteFlow(context.Background(), "AnyFlowApiName", nil, user)
	if err != nil {
		t.Fatalf("ExecuteFlow returned error: %v", err)
	}
	if q.capturedCtx == nil {
		t.Fatal("downstream Query() was never invoked — ExecuteFlow entry boundary not exercised")
	}
	audit.AssertEntrypointInContext(t, q.capturedCtx, audit.EntrypointFlow)
}

// TestExecuteFlow_OverridesIncomingEntrypoint asserts §3.5 stack-top
// semantics: a Flow invoked from a TRIGGER ctx must surface as FLOW inside
// the flow body, not TRIGGER. The last writer wins at the leaf.
func TestExecuteFlow_OverridesIncomingEntrypoint(t *testing.T) {
	q := &ctxCapturingQuery{}
	executor := &FlowExecutor{query: q}
	user := &models.UserSession{ID: "user-1"}

	triggerCtx := audit.WithEntrypoint(context.Background(), audit.EntrypointTrigger)
	if _, err := executor.ExecuteFlow(triggerCtx, "AnyFlow", nil, user); err != nil {
		t.Fatalf("ExecuteFlow returned error: %v", err)
	}
	if q.capturedCtx == nil {
		t.Fatal("Query() never invoked")
	}
	audit.AssertEntrypointInContext(t, q.capturedCtx, audit.EntrypointFlow)
}

// TestExecuteRecordTriggerFlows_StampsEntrypointTrigger asserts the C9
// contract for the trigger dispatch boundary: mutations performed while
// evaluating trigger criteria (before a Flow is invoked) should surface as
// TRIGGER on ctx. We observe this via the metadata lookup ctx captured by
// a stub GetFlowDefinitions call; since metadata.MetadataService is a
// concrete type (not an interface) in this package, we assert the contract
// by calling the ctx-stamp helper directly on executeRecordTriggerFlows's
// first statement via a smaller surrogate: verify the ctx-stamp helper
// produces the expected Entrypoint regardless of incoming ctx state.
//
// This complements the unit tests in package audit (which cover
// WithEntrypoint / EntrypointFromContext). The full integration assertion
// (mutations observed under EntrypointTrigger) will land with the
// PersistenceService wiring commit.
func TestExecuteRecordTriggerFlows_TriggerCtxContract(t *testing.T) {
	// Demonstrate the invariant: after wrapping ctx with Trigger, a
	// downstream consumer reads TRIGGER. The executor entry point relies on
	// this helper (see flow_trigger_listener.go:executeRecordTriggerFlows).
	base := context.Background()
	wrapped := audit.WithEntrypoint(base, audit.EntrypointTrigger)
	audit.AssertEntrypointInContext(t, wrapped, audit.EntrypointTrigger)

	// Also exercise the full trigger-dispatch happy-path through the
	// no-op code path (unknown event type → early return nil). This runs
	// the entry-boundary ctx stamp but does not require a metadata stack.
	listener := &FlowTriggerListener{}
	err := listener.executeRecordTriggerFlows(base, sharedEvents.EventType("no-such-event"), events.RecordEventPayload{})
	if err != nil {
		t.Fatalf("unexpected error on unknown event: %v", err)
	}
}
