package records

import (
	"context"
	"errors"
	"testing"

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

// formulaFetchStubRepo records calls into FindOne so the preview-provenance
// short-circuit can be asserted by absence-of-call. ReturnedRecord controls
// what the durable read path would surface when the short-circuit does NOT
// fire; ReturnErr lets the test simulate transient repo failures.
type formulaFetchStubRepo struct {
	findOneCalls   int
	returnedRecord models.SObject
	returnErr      error
}

func (r *formulaFetchStubRepo) FindOne(ctx context.Context, tableName string, id string) (models.SObject, error) {
	r.findOneCalls++
	if r.returnErr != nil {
		return nil, r.returnErr
	}
	return r.returnedRecord, nil
}

// Under no-DML insert preview preparation, recordsRelationFetchDeps.FetchByID
// must read a registered synthetic parent from the request-local registry
// instead of hitting the durable repo. Models a formula like Account__r.Name
// when the in-flight row's AccountId is itself a same-run preview-* ID.
func TestFormulaFetchByIDUsesSyntheticRecordUnderPreview(t *testing.T) {
	repo := &formulaFetchStubRepo{}
	deps := &recordsRelationFetchDeps{schema: nil, repo: repo}

	ctx := constants.WithInsertPreviewPreparation(context.Background())
	ctx = constants.WithPreviewSyntheticRecordRegistry(ctx)
	constants.RegisterPreviewSyntheticRecord(ctx, "preview-Account-1", "Account", map[string]interface{}{
		constants.FieldID: "preview-Account-1",
		"Name":            "Synthetic Acme",
	})

	got, err := deps.FetchByID(ctx, "Account", "preview-Account-1")
	if err != nil {
		t.Fatalf("FetchByID: %v", err)
	}
	if got == nil {
		t.Fatal("FetchByID returned nil; want synthetic record map")
	}
	if got["Name"] != "Synthetic Acme" {
		t.Fatalf("FetchByID Name = %v; want %q", got["Name"], "Synthetic Acme")
	}
	if repo.findOneCalls != 0 {
		t.Fatalf("repo.FindOne called %d time(s); want 0 (synthetic path must not hit durable repo)", repo.findOneCalls)
	}
}

// Anti-bypass: an unregistered preview-* literal under preview preparation
// must fall through to the durable repo. With a stub repo that returns
// (nil, nil), the formula engine sees a definitive-null parent — exactly
// the same semantics it would see if the FK pointed at a missing real
// record. No silent forgery from a hand-crafted preview-* string.
func TestFormulaFetchByIDRejectsUnregisteredPreviewIDUnderPreview(t *testing.T) {
	repo := &formulaFetchStubRepo{}
	deps := &recordsRelationFetchDeps{schema: nil, repo: repo}

	ctx := constants.WithInsertPreviewPreparation(context.Background())
	ctx = constants.WithPreviewSyntheticRecordRegistry(ctx)
	// No RegisterPreviewSyntheticRecord — the literal is unbacked.

	got, err := deps.FetchByID(ctx, "Account", "preview-Account-1")
	if err != nil {
		t.Fatalf("FetchByID: %v", err)
	}
	if got != nil {
		t.Fatalf("FetchByID = %v; want nil (unregistered preview-* must not synthesise a parent)", got)
	}
	if repo.findOneCalls != 1 {
		t.Fatalf("repo.FindOne called %d time(s); want 1 (unregistered preview-* must fall through to durable repo)", repo.findOneCalls)
	}
}

// Regression guard: without HasInsertPreviewPreparation on ctx the
// FetchByID must NEVER consult the registry even if one is attached. The
// caller contract for non-preview reads is "durable storage is authoritative"
// — a stray registry must not silently affect formula compute outside the
// no-DML preview path.
func TestFormulaFetchByIDIgnoresRegistryOutsidePreviewContext(t *testing.T) {
	repo := &formulaFetchStubRepo{
		returnedRecord: models.SObject{
			constants.FieldID: "001-Account-Real",
			"Name":            "Durable Acme",
		},
	}
	deps := &recordsRelationFetchDeps{schema: nil, repo: repo}

	// Registry attached but no HasInsertPreviewPreparation marker.
	ctx := context.Background()
	ctx = constants.WithPreviewSyntheticRecordRegistry(ctx)
	constants.RegisterPreviewSyntheticRecord(ctx, "preview-Account-1", "Account", map[string]interface{}{
		constants.FieldID: "preview-Account-1",
		"Name":            "Should Not Be Used",
	})

	got, err := deps.FetchByID(ctx, "Account", "001-Account-Real")
	if err != nil {
		t.Fatalf("FetchByID: %v", err)
	}
	if got == nil || got["Name"] != "Durable Acme" {
		t.Fatalf("FetchByID Name = %v; want %q (non-preview ctx must consult durable repo)", got, "Durable Acme")
	}
	if repo.findOneCalls != 1 {
		t.Fatalf("repo.FindOne called %d time(s); want 1", repo.findOneCalls)
	}
}

// Sanity: durable repo errors still surface unchanged when the synthetic
// short-circuit does not fire. Ensures the new branch is purely additive on
// the success-or-null path and does not swallow transient errors.
func TestFormulaFetchByIDPropagatesRepoErrorWhenNoSyntheticMatch(t *testing.T) {
	wantErr := errors.New("transient repo failure")
	repo := &formulaFetchStubRepo{returnErr: wantErr}
	deps := &recordsRelationFetchDeps{schema: nil, repo: repo}

	ctx := constants.WithInsertPreviewPreparation(context.Background())
	ctx = constants.WithPreviewSyntheticRecordRegistry(ctx)

	_, err := deps.FetchByID(ctx, "Account", "001-real-account")
	if !errors.Is(err, wantErr) {
		t.Fatalf("FetchByID err = %v; want wrapping %v", err, wantErr)
	}
}
