package automationintegration_test

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"testing"
	"time"

	automation "github.com/nexuscrm/automation"
	"github.com/nexuscrm/backend/internal/application/services"
	"github.com/nexuscrm/db"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-controlplane"
)

func TestStdConvertLead_RelinksOpenTaskChildrenUsingDerivedIsClosed(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-823 task isclosed derived-state integration test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue823",
		"LastName":  "TaskRelink",
		"Company":   fmt.Sprintf("Issue823 Co %d", time.Now().UnixNano()),
		"Status":    "Qualified",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	openTask, err := sm.Persistence.Insert(ctx, "Task", models.SObject{
		"Subject": "Issue823 Open Task",
		"Status":  "Not Started",
		"WhoId":   leadID,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert open Task: %v", err)
	}
	openTaskID, _ := openTask["Id"].(string)
	if openTaskID == "" {
		t.Fatal("insert open Task returned empty Id")
	}

	closedTask, err := sm.Persistence.Insert(ctx, "Task", models.SObject{
		"Subject": "Issue823 Closed Task",
		"Status":  "Completed",
		"WhoId":   leadID,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert closed Task: %v", err)
	}
	closedTaskID, _ := closedTask["Id"].(string)
	if closedTaskID == "" {
		t.Fatal("insert closed Task returned empty Id")
	}

	queryTask := func(taskID string) models.SObject {
		t.Helper()
		result, err := sm.QuerySvc.QueryWithFilter(ctx, "Task", fmt.Sprintf("Id = '%s'", taskID), systemAdminSession(), "", "", 1)
		if err != nil {
			t.Fatalf("QueryWithFilter(Task, Id=%s): %v", taskID, err)
		}
		if result == nil || len(result.Records) != 1 {
			t.Fatalf("QueryWithFilter(Task, Id=%s) records = %#v, want 1", taskID, result)
		}
		return result.Records[0]
	}

	readIsClosed := func(record models.SObject, label string) bool {
		t.Helper()
		got, ok := record["IsClosed"].(bool)
		if !ok {
			t.Fatalf("%s IsClosed = %#v (%T), want bool", label, record["IsClosed"], record["IsClosed"])
		}
		return got
	}

	openRecord := queryTask(openTaskID)
	if got := readIsClosed(openRecord, "open Task"); got {
		t.Fatal("open Task.IsClosed = true, want false")
	}

	closedRecord := queryTask(closedTaskID)
	if got := readIsClosed(closedRecord, "closed Task"); !got {
		t.Fatal("closed Task.IsClosed = false, want true")
	}

	filterResult, err := sm.QuerySvc.QueryWithFilter(ctx, "Task", fmt.Sprintf("WhoId = '%s' AND IsClosed = false", leadID), systemAdminSession(), "", "", 10)
	if err != nil {
		t.Fatalf("QueryWithFilter(Task, IsClosed=false): %v", err)
	}
	if filterResult == nil || len(filterResult.Records) != 1 {
		t.Fatalf("QueryWithFilter(Task, IsClosed=false) records = %#v, want 1 open task", filterResult)
	}
	if got, _ := filterResult.Records[0]["Id"].(string); got != openTaskID {
		t.Fatalf("QueryWithFilter(Task, IsClosed=false) Id = %q, want %q", got, openTaskID)
	}

	flowCtx := constants.WithStandardActionExecutionRootFlow(ctx, "sa_issue823_convert", "Std_ConvertLead")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":                 leadID,
		"convertedStatus":        "Converted",
		"doNotCreateOpportunity": true,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v", err)
	}
	if result == nil || !result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want success", result)
	}

	contactID, _ := result.OutputValues["contactId"].(string)
	if strings.TrimSpace(contactID) == "" {
		t.Fatalf("contactId output = %q, want non-empty", contactID)
	}

	var openWhoID string
	if err := conn.DB().QueryRowContext(ctx, "SELECT `WhoId` FROM `Task` WHERE `Id` = ?", openTaskID).Scan(&openWhoID); err != nil {
		t.Fatalf("query open Task.WhoId: %v", err)
	}
	if openWhoID != contactID {
		t.Fatalf("open Task.WhoId = %q, want converted Contact %q", openWhoID, contactID)
	}

	var closedWhoID string
	if err := conn.DB().QueryRowContext(ctx, "SELECT `WhoId` FROM `Task` WHERE `Id` = ?", closedTaskID).Scan(&closedWhoID); err != nil {
		t.Fatalf("query closed Task.WhoId: %v", err)
	}
	if closedWhoID != leadID {
		t.Fatalf("closed Task.WhoId = %q, want original Lead %q", closedWhoID, leadID)
	}
}

func TestStdConvertLead_BeforeSaveMutationFeedsDownstreamCreates(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-349 mutation integration test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	installLeadBeforeSaveFlow(t, ctx, sm, "Issue349MutateLeadBeforeSave", leadBeforeSaveMutationFlowMetadata())
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("RefreshMetadataCache after flow install: %v", err)
	}

	leadCompany := fmt.Sprintf("Issue349 Original Co %d", time.Now().UnixNano())
	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue349",
		"LastName":  "Mutation",
		"Company":   leadCompany,
		"Status":    "Qualified",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	flowCtx := constants.WithStandardActionExecutionRootFlow(ctx, "sa_issue349_convert", "Std_ConvertLead")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":                 leadID,
		"convertedStatus":        "Converted",
		"doNotCreateOpportunity": true,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v", err)
	}
	if result == nil || !result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want success", result)
	}

	accountID, _ := result.OutputValues["accountId"].(string)
	if accountID == "" {
		t.Fatalf("accountId output = %q, want non-empty", accountID)
	}

	// After the fix (issue-552), PreviewLeadBeforeSaveUpdate runs AFTER
	// CreateAccount, so CreateAccount uses the pre-before-save Lead.Company.
	// The Lead record itself still gets mutated by the before-save flow.
	// This matches Salesforce v63: a Lead before-save flow modifies only the
	// Lead record, not the newly-created Account or Contact.
	wantLeadCompany := "Flow Mutated Company"
	gotAccountName := queryString(t, conn.DB(), `SELECT Name FROM Account WHERE Id = ?`, accountID)
	if gotAccountName != leadCompany {
		t.Fatalf("created Account.Name = %q, want pre-before-save Lead.Company %q (SF v63: before-save flow does not propagate to new Account)", gotAccountName, leadCompany)
	}

	gotLeadCompany := queryString(t, conn.DB(), "SELECT `Company` FROM `Lead` WHERE `Id` = ?", leadID)
	if gotLeadCompany != wantLeadCompany {
		t.Fatalf("Lead.Company after convert = %q, want %q", gotLeadCompany, wantLeadCompany)
	}
}

func TestStdConvertLead_BeforeSaveFlowCustomErrorAbortsBeforeDownstreamWrites(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-349 customerror integration test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	installLeadBeforeSaveFlow(t, ctx, sm, "Issue349BlockLeadBeforeSave", leadBeforeSaveCustomErrorFlowMetadata())
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("RefreshMetadataCache after flow install: %v", err)
	}

	leadCompany := fmt.Sprintf("Issue349 Blocked Co %d", time.Now().UnixNano())
	leadLastName := fmt.Sprintf("Blocked-%d", time.Now().UnixNano())
	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue349",
		"LastName":  leadLastName,
		"Company":   leadCompany,
		"Status":    "Open - Not Contacted",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	flowCtx := constants.WithStandardActionExecutionRootFlow(ctx, "sa_issue349_convert", "Std_ConvertLead")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":                 leadID,
		"convertedStatus":        "Converted",
		"doNotCreateOpportunity": true,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v, want nil flow result with IsSuccess=false", err)
	}
	if result == nil || result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want IsSuccess=false", result)
	}
	if len(result.Errors) == 0 {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) errors = %#v, want customError payload", result)
	}
	if got := result.Errors[0].StatusCode; got != "FIELD_CUSTOM_VALIDATION_EXCEPTION" {
		t.Fatalf("first error statusCode = %q, want FIELD_CUSTOM_VALIDATION_EXCEPTION", got)
	}
	if got := result.Errors[0].Message; got != "Lead must be Qualified before convert" {
		t.Fatalf("first error message = %q", got)
	}

	if got := countRows(t, conn.DB(), `SELECT COUNT(*) FROM Account WHERE Name = ? AND IsDeleted = 0`, leadCompany); got != 0 {
		t.Fatalf("Account rows committed = %d, want 0 after before-save customError", got)
	}
	if got := countRows(t, conn.DB(), `SELECT COUNT(*) FROM Contact WHERE LastName = ? AND IsDeleted = 0`, leadLastName); got != 0 {
		t.Fatalf("Contact rows committed = %d, want 0 after before-save customError", got)
	}
	if got := countRows(t, conn.DB(), `SELECT COUNT(*) FROM Opportunity WHERE Name = ? AND IsDeleted = 0`, leadCompany); got != 0 {
		t.Fatalf("Opportunity rows committed = %d, want 0 after before-save customError", got)
	}
	var isConverted sql.NullBool
	if err := conn.DB().QueryRowContext(ctx, "SELECT `IsConverted` FROM `Lead` WHERE `Id` = ?", leadID).Scan(&isConverted); err != nil {
		t.Fatalf("query Lead.IsConverted: %v", err)
	}
	if isConverted.Valid && isConverted.Bool {
		t.Fatal("Lead.IsConverted = true after failed convert, want false")
	}
}

func TestStdConvertLead_SourceLeadFinalUpdateEnqueuesRecordUpdatedOutboxEvent(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-349 post-update lifecycle integration test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	leadCompany := fmt.Sprintf("Issue349 AfterSave Source %d", time.Now().UnixNano())
	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue349",
		"LastName":  "AfterSave",
		"Company":   leadCompany,
		"Status":    "Qualified",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	flowCtx := constants.WithStandardActionExecutionRootFlow(ctx, "sa_issue349_convert", "Std_ConvertLead")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":                 leadID,
		"convertedStatus":        "Converted",
		"doNotCreateOpportunity": true,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v", err)
	}
	if result == nil || !result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want success", result)
	}

	if got := countRows(t, conn.DB(), `SELECT COUNT(*) FROM _System_OutboxEvent WHERE EventType = ? AND Payload LIKE ?`, "record.updated", "%"+leadID+"%"); got != 1 {
		t.Fatalf("record.updated outbox rows for converted Lead = %d, want 1", got)
	}
}

func TestStdConvertLead_BeforeSaveFilteredRecordUpdateRejectedWithoutSideEffects(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-349 invalid before-save filtered update integration test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	targetCompany := fmt.Sprintf("Issue349 Target Co %d", time.Now().UnixNano())
	targetLead, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue349",
		"LastName":  "Target",
		"Company":   targetCompany,
		"Status":    "Qualified",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert target Lead: %v", err)
	}
	targetLeadID, _ := targetLead["Id"].(string)
	if targetLeadID == "" {
		t.Fatal("insert target Lead returned empty Id")
	}

	installLeadBeforeSaveFlow(t, ctx, sm, "Issue349InvalidBeforeSaveFilteredUpdate", leadBeforeSaveFilteredRecordUpdateFlowMetadata(targetLeadID))
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("RefreshMetadataCache after flow install: %v", err)
	}

	sourceCompany := fmt.Sprintf("Issue349 Source Co %d", time.Now().UnixNano())
	sourceLastName := fmt.Sprintf("Source-%d", time.Now().UnixNano())
	sourceLead, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue349",
		"LastName":  sourceLastName,
		"Company":   sourceCompany,
		"Status":    "Qualified",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert source Lead: %v", err)
	}
	sourceLeadID, _ := sourceLead["Id"].(string)
	if sourceLeadID == "" {
		t.Fatal("insert source Lead returned empty Id")
	}

	flowCtx := constants.WithStandardActionExecutionRootFlow(ctx, "sa_issue349_convert", "Std_ConvertLead")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":                 sourceLeadID,
		"convertedStatus":        "Converted",
		"doNotCreateOpportunity": true,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v, want invalid-flow result", err)
	}
	if result == nil || result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want IsSuccess=false", result)
	}
	if len(result.Errors) == 0 {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) errors = %#v, want INVALID_FLOW payload", result)
	}
	if got := result.Errors[0].StatusCode; got != "INVALID_FLOW" {
		t.Fatalf("first error statusCode = %q, want INVALID_FLOW", got)
	}

	if got := queryString(t, conn.DB(), "SELECT `Company` FROM `Lead` WHERE `Id` = ?", targetLeadID); got != targetCompany {
		t.Fatalf("target Lead.Company = %q, want %q after rejected before-save flow", got, targetCompany)
	}
	if got := countRows(t, conn.DB(), `SELECT COUNT(*) FROM Account WHERE Name = ? AND IsDeleted = 0`, sourceCompany); got != 0 {
		t.Fatalf("Account rows committed = %d, want 0 after invalid before-save flow", got)
	}
	if got := countRows(t, conn.DB(), `SELECT COUNT(*) FROM Contact WHERE LastName = ? AND IsDeleted = 0`, sourceLastName); got != 0 {
		t.Fatalf("Contact rows committed = %d, want 0 after invalid before-save flow", got)
	}
	var isConverted sql.NullBool
	if err := conn.DB().QueryRowContext(ctx, "SELECT `IsConverted` FROM `Lead` WHERE `Id` = ?", sourceLeadID).Scan(&isConverted); err != nil {
		t.Fatalf("query source Lead.IsConverted: %v", err)
	}
	if isConverted.Valid && isConverted.Bool {
		t.Fatal("source Lead.IsConverted = true after invalid before-save flow, want false")
	}
}

func TestStdConvertLead_BeforeSaveWhitespaceIsBlankDecisionBranch(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-1205 whitespace IsBlank branch integration test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	installLeadBeforeSaveFlow(t, ctx, sm, "Issue1205WhitespaceIsBlank", leadBeforeSaveWhitespaceIsBlankFlowMetadata())
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("RefreshMetadataCache after flow install: %v", err)
	}

	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue1205",
		"LastName":  "OriginalLastName",
		"Company":   "   ",
		"Status":    "Open - Not Contacted",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	if err := sm.Persistence.Update(ctx, "Lead", leadID, models.SObject{
		"Status": "Working - Contacted",
	}, systemAdminSession()); err != nil {
		t.Fatalf("Update(Lead) error = %v", err)
	}

	if got := queryString(t, conn.DB(), "SELECT `LastName` FROM `Lead` WHERE `Id` = ?", leadID); got != "BLANK_BRANCH" {
		t.Fatalf("Lead.LastName after whitespace IsBlank flow = %q, want %q", got, "BLANK_BRANCH")
	}
}

func TestStdConvertLead_BeforeSaveFlowActivatedViaToolingIsVisibleMidSession(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-350 live tooling activation parity test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	patchLead, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue350",
		"LastName":  "PatchProbe",
		"Company":   "Issue350 Patch Original Co",
		"Status":    "Open - Not Contacted",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert patch Lead: %v", err)
	}
	patchLeadID, _ := patchLead["Id"].(string)
	if patchLeadID == "" {
		t.Fatal("insert patch Lead returned empty Id")
	}

	convertLeadSource, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue350",
		"LastName":  "ConvertProbe",
		"Company":   "Issue350 Convert Original Co",
		"Status":    "Qualified",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert convert Lead: %v", err)
	}
	convertLeadID, _ := convertLeadSource["Id"].(string)
	if convertLeadID == "" {
		t.Fatal("insert convert Lead returned empty Id")
	}

	const developerName = "Issue350ToolingActivatedLeadBeforeSave"
	const mutatedCompany = "Issue350 Tooling Activated Co"
	versionID := createLeadBeforeSaveFlowVersion(t, ctx, sm, developerName, leadBeforeSaveAlwaysMutateCompanyFlowMetadata(mutatedCompany), models.FlowStatusDraft)
	if err := sm.Metadata.ActivateFlowVersion(ctx, versionID); err != nil {
		t.Fatalf("ActivateFlowVersion(%s): %v", versionID, err)
	}
	// Intentionally do NOT call RefreshMetadataCache here. The regression is
	// that both the PATCH path and the convertLead preview/finalize path must
	// pick up the active flow through the same live runtime lookup.

	if err := sm.Persistence.Update(ctx, "Lead", patchLeadID, models.SObject{
		"Status": "Working - Contacted",
	}, systemAdminSession()); err != nil {
		t.Fatalf("Update(Lead) after activation: %v", err)
	}
	if got := queryString(t, conn.DB(), "SELECT `Company` FROM `Lead` WHERE `Id` = ?", patchLeadID); got != mutatedCompany {
		t.Fatalf("Lead PATCH path Company = %q, want %q after mid-session activation", got, mutatedCompany)
	}

	flowCtx := constants.WithStandardActionExecutionRootFlow(ctx, "sa_issue350_convert", "Std_ConvertLead")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":                 convertLeadID,
		"convertedStatus":        "Converted",
		"doNotCreateOpportunity": true,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v", err)
	}
	if result == nil || !result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want success", result)
	}

	accountID, _ := result.OutputValues["accountId"].(string)
	if accountID == "" {
		t.Fatalf("accountId output = %q, want non-empty", accountID)
	}
	if got := queryString(t, conn.DB(), "SELECT `Company` FROM `Lead` WHERE `Id` = ?", convertLeadID); got != mutatedCompany {
		t.Fatalf("convertLead source Lead.Company = %q, want %q after mid-session activation", got, mutatedCompany)
	}
	// After the fix (issue-552), PreviewLeadBeforeSaveUpdate runs AFTER
	// CreateAccount. The Account uses the pre-before-save Lead.Company.
	// The Lead itself still gets mutated (asserted above on convertLeadID).
	convertOriginalCompany := "Issue350 Convert Original Co"
	if got := queryString(t, conn.DB(), "SELECT `Name` FROM `Account` WHERE `Id` = ?", accountID); got != convertOriginalCompany {
		t.Fatalf("convertLead created Account.Name = %q, want pre-before-save %q after mid-session activation", got, convertOriginalCompany)
	}
}

func installLeadBeforeSaveFlow(t *testing.T, ctx context.Context, sm *services.ServiceManager, developerName string, meta models.FlowMetadata) {
	t.Helper()

	def := &models.FlowDefinition{
		DeveloperName: developerName,
		MasterLabel:   developerName,
		Description:   "issue-349 test flow",
	}
	if err := sm.Metadata.CreateFlowDefinitionForLifecycle(ctx, def); err != nil {
		t.Fatalf("CreateFlowDefinitionForLifecycle(%s): %v", developerName, err)
	}
	version := &models.FlowVersion{
		DefinitionId: def.Id,
		MasterLabel:  developerName,
		Status:       models.FlowStatusActive,
		ProcessType:  "AutoLaunchedFlow",
		Metadata:     meta,
	}
	if err := sm.Metadata.CreateFlowVersion(ctx, version); err != nil {
		t.Fatalf("CreateFlowVersion(%s): %v", developerName, err)
	}
}

func createLeadBeforeSaveFlowVersion(t *testing.T, ctx context.Context, sm *services.ServiceManager, developerName string, meta models.FlowMetadata, status string) string {
	t.Helper()

	def := &models.FlowDefinition{
		DeveloperName: developerName,
		MasterLabel:   developerName,
		Description:   "issue-350 test flow",
	}
	if err := sm.Metadata.CreateFlowDefinitionForLifecycle(ctx, def); err != nil {
		t.Fatalf("CreateFlowDefinitionForLifecycle(%s): %v", developerName, err)
	}
	version := &models.FlowVersion{
		DefinitionId: def.Id,
		MasterLabel:  developerName,
		Status:       status,
		ProcessType:  "AutoLaunchedFlow",
		Metadata:     meta,
	}
	if err := sm.Metadata.CreateFlowVersion(ctx, version); err != nil {
		t.Fatalf("CreateFlowVersion(%s): %v", developerName, err)
	}
	return version.Id
}

func leadBeforeSaveWhitespaceIsBlankFlowMetadata() models.FlowMetadata {
	return models.FlowMetadata{
		Start: &models.FlowStart{
			TriggerType:       "RecordBeforeSave",
			RecordTriggerType: "Update",
			Object:            "Lead",
			Connector:         &models.FlowConnector{TargetReference: "CheckWhitespaceBlank"},
		},
		Decisions: []models.FlowDecision{
			{
				Name:  "CheckWhitespaceBlank",
				Label: "Whitespace company is blank",
				Rules: []models.FlowDecisionRule{
					{
						Name:           "WhitespaceIsBlank",
						ConditionLogic: "and",
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.Company", Operator: "IsBlank", RightValue: true},
						},
						Connector: &models.FlowConnector{TargetReference: "MarkBlankBranch"},
					},
				},
				DefaultConnector: &models.FlowConnector{TargetReference: "MarkNotBlankBranch"},
			},
		},
		Assignments: []models.FlowAssignment{
			{
				Name:  "MarkBlankBranch",
				Label: "Whitespace routed to blank branch",
				Items: []models.FlowAssignmentItem{
					{AssignToReference: "$Record.LastName", Operator: "Assign", Value: "BLANK_BRANCH"},
				},
			},
			{
				Name:  "MarkNotBlankBranch",
				Label: "Whitespace routed to non-blank branch",
				Items: []models.FlowAssignmentItem{
					{AssignToReference: "$Record.LastName", Operator: "Assign", Value: "NOT_BLANK_BRANCH"},
				},
			},
		},
	}
}

func setupConvertLeadRuntime(t *testing.T, ctx context.Context) (*database.TiDBConnection, *services.ServiceManager) {
	t.Helper()

	conn, sm := setupRecordTriggerRuntime(t, ctx)
	runner, err := controlplane.NewRunner(conn)
	if err != nil {
		t.Fatalf("NewRunner: %v", err)
	}
	setOfficialPackRootForAutomationTest(t)
	registerCRMDistributionHostPrimitivesForTest(t)
	if err := controlplane.InstallPackFromSource(ctx, runner, "official_repo:salesforce-crm-standard"); err != nil {
		t.Fatalf("InstallPackFromSource(salesforce-crm-standard): %v", err)
	}
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("RefreshMetadataCache after install-pack: %v", err)
	}
	return conn, sm
}

func standardActionSystemReadScopeContext(ctx context.Context, actionID string) context.Context {
	ctx = constants.WithStandardActionInternalAccessScopes(ctx, actionID, []constants.StandardActionInternalAccessScope{{
		StandardActionID: actionID,
		ObjectAPIName:    "Opportunity",
		Operation:        constants.PermRead,
	}})
	ctx = constants.WithStandardActionExecutionRootFlow(ctx, actionID, "Std_ConvertLead")
	ctx = constants.WithStandardActionProtectedFlowExecution(ctx, "Std_ConvertLead")
	return constants.WithSecurityContext(ctx, constants.SystemContext, "standard action internal read scope test")
}

func standardActionSystemOnlyContext(ctx context.Context, actionID string) context.Context {
	ctx = constants.WithStandardActionExecutionRootFlow(ctx, actionID, "Std_ConvertLead")
	ctx = constants.WithStandardActionProtectedFlowExecution(ctx, "Std_ConvertLead")
	return constants.WithSecurityContext(ctx, constants.SystemContext, "standard action system context without internal read scope test")
}

func leadBeforeSaveMutationFlowMetadata() models.FlowMetadata {
	return models.FlowMetadata{
		Start: &models.FlowStart{
			TriggerType:       "RecordBeforeSave",
			RecordTriggerType: "Update",
			Object:            "Lead",
			Connector:         &models.FlowConnector{TargetReference: "ShouldMutateLead"},
		},
		Decisions: []models.FlowDecision{
			{
				Name:  "ShouldMutateLead",
				Label: "Mutate converted lead company",
				Rules: []models.FlowDecisionRule{
					{
						Name:           "LeadIsBeingConverted",
						ConditionLogic: "and",
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.IsConverted", Operator: "EqualTo", RightValue: true},
						},
						Connector: &models.FlowConnector{TargetReference: "MutateLeadCompany"},
					},
				},
			},
		},
		Assignments: []models.FlowAssignment{
			{
				Name:  "MutateLeadCompany",
				Label: "Rewrite company",
				Items: []models.FlowAssignmentItem{
					{
						AssignToReference: "$Record.Company",
						Operator:          "Assign",
						Value:             "Flow Mutated Company",
					},
				},
			},
		},
	}
}

func leadBeforeSaveCustomErrorFlowMetadata() models.FlowMetadata {
	return models.FlowMetadata{
		Start: &models.FlowStart{
			TriggerType:       "RecordBeforeSave",
			RecordTriggerType: "Update",
			Object:            "Lead",
			Connector:         &models.FlowConnector{TargetReference: "RejectUnqualifiedConvert"},
		},
		Decisions: []models.FlowDecision{
			{
				Name:  "RejectUnqualifiedConvert",
				Label: "Reject non-qualified convert",
				Rules: []models.FlowDecisionRule{
					{
						Name:           "NonQualifiedConvert",
						ConditionLogic: "and",
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$Record.IsConverted", Operator: "EqualTo", RightValue: true},
							{LeftValueReference: "$Record.Status", Operator: "NotEqualTo", RightValue: "Qualified"},
						},
						Connector: &models.FlowConnector{TargetReference: "RejectConvert"},
					},
				},
			},
		},
		CustomErrors: []models.FlowCustomError{
			{
				Name:  "RejectConvert",
				Label: "Reject convert",
				CustomErrorMessages: []models.FlowCustomErrorMessage{
					{
						ErrorMessage: "Lead must be Qualified before convert",
						StatusCode:   "FIELD_CUSTOM_VALIDATION_EXCEPTION",
					},
				},
			},
		},
	}
}

func leadBeforeSaveFilteredRecordUpdateFlowMetadata(targetLeadID string) models.FlowMetadata {
	return models.FlowMetadata{
		Start: &models.FlowStart{
			TriggerType:       "RecordBeforeSave",
			RecordTriggerType: "Update",
			Object:            "Lead",
			Connector:         &models.FlowConnector{TargetReference: "UpdateSiblingLead"},
		},
		RecordUpdates: []models.FlowRecordUpdate{
			{
				Name:   "UpdateSiblingLead",
				Object: "Lead",
				Filters: []models.FlowRecordFilter{
					{Field: "Id", Operator: "EqualTo", Value: map[string]interface{}{"stringValue": targetLeadID}},
				},
				InputAssignments: []models.FlowInputAssignment{
					{Field: "Company", Value: map[string]interface{}{"stringValue": "Mutated by invalid before-save flow"}},
				},
			},
		},
	}
}

func leadBeforeSaveAlwaysMutateCompanyFlowMetadata(company string) models.FlowMetadata {
	return models.FlowMetadata{
		Start: &models.FlowStart{
			TriggerType:       "RecordBeforeSave",
			RecordTriggerType: "Update",
			Object:            "Lead",
			Connector:         &models.FlowConnector{TargetReference: "MutateLeadCompany"},
		},
		Assignments: []models.FlowAssignment{
			{
				Name:  "MutateLeadCompany",
				Label: "Always rewrite company",
				Items: []models.FlowAssignmentItem{
					{
						AssignToReference: "$Record.Company",
						Operator:          "Assign",
						Value:             company,
					},
				},
			},
		},
	}
}

// TestStdConvertLead_IssueI467_PreparedSourcePreviewNotLostBeforeCommit
// is a characterization test that documents the expected end-to-end behaviour of
// convertLead when no before-save flows exist. It guards against regression of the
// prepared source preview lifecycle: executePreviewRecordUpdate must store the
// prepared update in the flow runtime context so the product-owned
// commitPreparedRecordUpdate action can read it back and commit the explicit
// anchor stamps
// (DEC2 of issue-467 design.md).
func TestStdConvertLead_IssueI467_PreparedSourcePreviewNotLostBeforeCommit(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-467 prepared source preview regression test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	// No before-save flows — the plain no-mutation convertLead path.
	// Exercises the previewRecordUpdate → commitPreparedRecordUpdate chain directly.
	leadCompany := fmt.Sprintf("Issue467 Co %d", time.Now().UnixNano())
	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue467",
		"LastName":  "Regression",
		"Company":   leadCompany,
		"Status":    "Qualified",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	flowCtx := constants.WithStandardActionExecutionRootFlow(ctx, "sa_issue467_convert", "Std_ConvertLead")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":                 leadID,
		"convertedStatus":        "Closed - Converted",
		"doNotCreateOpportunity": true,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v", err)
	}
	if result == nil || !result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want IsSuccess=true; commitPreparedRecordUpdate must find prepared source preview in FlowRuntimeContext", result)
	}

	// Verify that commitPreparedRecordUpdate committed the anchor stamps to the DB.
	var isConverted sql.NullBool
	if err := conn.DB().QueryRowContext(ctx, "SELECT `IsConverted` FROM `Lead` WHERE `Id` = ?", leadID).Scan(&isConverted); err != nil {
		t.Fatalf("query Lead.IsConverted: %v", err)
	}
	if !isConverted.Valid || !isConverted.Bool {
		t.Fatalf("Lead.IsConverted = %v, want true after commitPreparedRecordUpdate", isConverted)
	}
	if got := queryString(t, conn.DB(), "SELECT `Status` FROM `Lead` WHERE `Id` = ?", leadID); got != "Closed - Converted" {
		t.Fatalf("Lead.Status = %q, want %q after commitPreparedRecordUpdate", got, "Closed - Converted")
	}
	accountID, _ := result.OutputValues["accountId"].(string)
	if accountID == "" {
		t.Fatalf("accountId output = %q, want non-empty; commitPreparedRecordUpdate must have committed prepared update with account anchor", accountID)
	}
}

// TestStdConvertLead_BeforeSaveFlowSeesConvertedAccountId verifies that a
// user-authored Lead before-save flow can read $Record.ConvertedAccountId
// during a convert operation (issue #552).
//
// The test installs a before-save flow that:
//  1. RecordLookup Account where Id == $Record.ConvertedAccountId
//  2. Decision: if $lookedAccount.Id IsNotBlank → assign $Record.Company = "ANCHOR_VISIBLE"
//     else → assign $Record.Company = "ANCHOR_MISSING"
//
// Before the fix, ConvertedAccountId was null at the time before-save fired, so
// the RecordLookup returned no rows, the Decision took the "missing" branch, and
// Lead.Company was set to "ANCHOR_MISSING". After the fix the anchor ID is staged
// before the event fires, the lookup finds the Account, and Company = "ANCHOR_VISIBLE".
func TestStdConvertLead_BeforeSaveFlowSeesConvertedAccountId(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-552 convert anchor visibility test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	// Create an Account to convert into so we have a known accountId.
	accountRecord, err := sm.Persistence.Insert(ctx, "Account", models.SObject{
		"Name": "Issue552 Target Account",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Account: %v", err)
	}
	targetAccountID, _ := accountRecord["Id"].(string)
	if targetAccountID == "" {
		t.Fatal("insert Account returned empty Id")
	}

	installLeadBeforeSaveFlow(t, ctx, sm, "Issue552AnchorVisibleBeforeSave", leadBeforeSaveConvertedAccountIdFlowMetadata())
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("RefreshMetadataCache after flow install: %v", err)
	}

	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue552",
		"LastName":  "AnchorVisible",
		"Company":   "Issue552 Original Co",
		"Status":    "Qualified",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	flowCtx := constants.WithStandardActionExecutionRootFlow(ctx, "sa_issue552_convert", "Std_ConvertLead")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":                 leadID,
		"accountId":              targetAccountID,
		"convertedStatus":        "Converted",
		"doNotCreateOpportunity": true,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v", err)
	}
	if result == nil || !result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want IsSuccess=true", result)
	}

	// The before-save flow set Company based on whether it could look up the
	// Account via $Record.ConvertedAccountId. "ANCHOR_VISIBLE" means the fix works.
	gotCompany := queryString(t, conn.DB(), "SELECT `Company` FROM `Lead` WHERE `Id` = ?", leadID)
	if gotCompany != "ANCHOR_VISIBLE" {
		t.Fatalf("Lead.Company after convert = %q, want %q; before-save flow must see ConvertedAccountId in $Record", gotCompany, "ANCHOR_VISIBLE")
	}
}

func TestStdConvertLead_BeforeSaveFlowSeesCreatedOpportunityInsideAtomicConvert(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-2098 atomic convert opportunity visibility test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	installLeadBeforeSaveFlow(t, ctx, sm, "Issue2098OpportunityVisibleBeforeSave", leadBeforeSaveConvertedOpportunityIdFlowMetadata())
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("RefreshMetadataCache after flow install: %v", err)
	}

	restrictedUser := &models.UserSession{
		ID:        "005-issue2098-restricted-user",
		Name:      "Issue2098 Restricted User",
		ProfileID: constants.ProfileStandardUser,
	}
	userCtx := constants.WithSecurityContext(context.Background(), constants.UserContext, "issue-2098 permission precondition")
	if !sm.Permissions.CheckObjectPermissionWithUser(userCtx, "Opportunity", constants.PermRead, restrictedUser) {
		t.Fatal("test precondition failed: Standard User must have Opportunity Read so lookup denial proves RLS, not object CRUD")
	}
	if sm.Permissions.CheckObjectPermissionWithUser(userCtx, "Opportunity", "ViewAllRecords", restrictedUser) {
		t.Fatal("test precondition failed: Standard User must not have Opportunity ViewAllRecords")
	}

	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue2098",
		"LastName":  "AtomicOpportunity",
		"Company":   "Issue2098 Original Co",
		"Status":    "Qualified",
		"OwnerId":   systemAdminSession().ID,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	flowCtx := standardActionSystemReadScopeContext(ctx, "sa_issue2098_convert")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":          leadID,
		"convertedStatus": "Converted",
		"opportunityName": "Issue2098 Converted Opportunity",
	}, restrictedUser)
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v", err)
	}
	if result == nil || !result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want IsSuccess=true", result)
	}

	convertedOpportunityID := queryString(t, conn.DB(), "SELECT COALESCE(`ConvertedOpportunityId`, '') FROM `Lead` WHERE `Id` = ?", leadID)
	if convertedOpportunityID == "" {
		t.Fatal("Lead.ConvertedOpportunityId after convert is empty")
	}
	opportunityOwnerID := queryString(t, conn.DB(), "SELECT COALESCE(`OwnerId`, '') FROM `Opportunity` WHERE `Id` = ?", convertedOpportunityID)
	if opportunityOwnerID == restrictedUser.ID {
		t.Fatalf("test precondition failed: Opportunity.OwnerId = restricted user %q; distinct-owner setup is required before scoped read assertion", restrictedUser.ID)
	}
	restrictedRead, err := sm.QuerySvc.QueryWithFilter(userCtx, "Opportunity", fmt.Sprintf("Id = '%s'", convertedOpportunityID), restrictedUser, "", "", 1)
	if err != nil {
		t.Fatalf("restricted QueryWithFilter(Opportunity) error = %v; want zero rows through object-readable user RLS", err)
	}
	if restrictedRead != nil && len(restrictedRead.Records) != 0 {
		t.Fatalf("restricted QueryWithFilter(Opportunity) returned %d row(s), want 0 for RLS-unreadable converted Opportunity", len(restrictedRead.Records))
	}

	gotCompany := queryString(t, conn.DB(), "SELECT `Company` FROM `Lead` WHERE `Id` = ?", leadID)
	if gotCompany != "OPPORTUNITY_VISIBLE" {
		t.Fatalf("Lead.Company after convert = %q, want %q; before-save flow must preserve standard-action internal read scope for same-transaction Opportunity via ConvertedOpportunityId", gotCompany, "OPPORTUNITY_VISIBLE")
	}
}

func TestStdConvertLead_BeforeSaveFlowDoesNotExposeUnreadableOpportunityInsideAtomicConvert(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-2204 atomic convert opportunity RLS regression test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	installLeadBeforeSaveFlow(t, ctx, sm, "Issue2204UnreadableOpportunityBeforeSave", leadBeforeSaveConvertedOpportunityIdFlowMetadata())
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("RefreshMetadataCache after flow install: %v", err)
	}

	restrictedUser := &models.UserSession{
		ID:        "005-issue2204-restricted-user",
		Name:      "Issue2204 Restricted User",
		ProfileID: constants.ProfileStandardUser,
	}
	userCtx := constants.WithSecurityContext(context.Background(), constants.UserContext, "issue-2204 permission precondition")
	if !sm.Permissions.CheckObjectPermissionWithUser(userCtx, "Opportunity", constants.PermRead, restrictedUser) {
		t.Fatal("test precondition failed: Standard User must have Opportunity Read so lookup denial proves RLS, not object CRUD")
	}
	if sm.Permissions.CheckObjectPermissionWithUser(userCtx, "Opportunity", "ViewAllRecords", restrictedUser) {
		t.Fatal("test precondition failed: Standard User must not have Opportunity ViewAllRecords")
	}

	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue2204",
		"LastName":  "AtomicOpportunityRLS",
		"Company":   "Issue2204 Original Co",
		"Status":    "Qualified",
		"OwnerId":   systemAdminSession().ID,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	flowCtx := standardActionSystemOnlyContext(ctx, "sa_issue2204_convert")
	if constants.HasStandardActionInternalAccessScopes(flowCtx) {
		t.Fatal("test precondition failed: system-only standard action context must not carry internal read scopes")
	}
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":          leadID,
		"convertedStatus": "Converted",
		"opportunityName": "Issue2204 Converted Opportunity",
	}, restrictedUser)
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v", err)
	}
	if result == nil || !result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want IsSuccess=true", result)
	}

	convertedOpportunityID := queryString(t, conn.DB(), "SELECT COALESCE(`ConvertedOpportunityId`, '') FROM `Lead` WHERE `Id` = ?", leadID)
	if convertedOpportunityID == "" {
		t.Fatal("Lead.ConvertedOpportunityId after convert is empty")
	}
	opportunityOwnerID := queryString(t, conn.DB(), "SELECT COALESCE(`OwnerId`, '') FROM `Opportunity` WHERE `Id` = ?", convertedOpportunityID)
	if opportunityOwnerID == restrictedUser.ID {
		t.Fatalf("test precondition failed: Opportunity.OwnerId = restricted user %q; distinct-owner setup is required before live RLS assertion", restrictedUser.ID)
	}
	restrictedRead, err := sm.QuerySvc.QueryWithFilter(userCtx, "Opportunity", fmt.Sprintf("Id = '%s'", convertedOpportunityID), restrictedUser, "", "", 1)
	if err != nil {
		t.Fatalf("restricted QueryWithFilter(Opportunity) error = %v; want zero rows through object-readable user RLS", err)
	}
	if restrictedRead != nil && len(restrictedRead.Records) != 0 {
		t.Fatalf("restricted QueryWithFilter(Opportunity) returned %d row(s), want 0 for RLS-unreadable converted Opportunity", len(restrictedRead.Records))
	}

	gotCompany := queryString(t, conn.DB(), "SELECT `Company` FROM `Lead` WHERE `Id` = ?", leadID)
	if gotCompany != "OPPORTUNITY_MISSING" {
		t.Fatalf("Lead.Company after convert = %q, want %q; owned-ID overlay must use invoking-user RLS despite ambient standard-action SystemContext", gotCompany, "OPPORTUNITY_MISSING")
	}
}

func TestStdConvertLead_RelinksOnlyOpenTasksByDerivedIsClosed(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-823 task isclosed relink regression test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue823",
		"LastName":  "TaskRelink",
		"Company":   fmt.Sprintf("Issue823 Co %d", time.Now().UnixNano()),
		"Status":    "Qualified",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	openTask, err := sm.Persistence.Insert(ctx, "Task", models.SObject{
		"Subject": "Issue823 Open Task",
		"Status":  "Not Started",
		"WhoId":   leadID,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert open Task: %v", err)
	}
	openTaskID, _ := openTask["Id"].(string)
	if openTaskID == "" {
		t.Fatal("insert open Task returned empty Id")
	}

	closedTask, err := sm.Persistence.Insert(ctx, "Task", models.SObject{
		"Subject": "Issue823 Closed Task",
		"Status":  "Completed",
		"WhoId":   leadID,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert closed Task: %v", err)
	}
	closedTaskID, _ := closedTask["Id"].(string)
	if closedTaskID == "" {
		t.Fatal("insert closed Task returned empty Id")
	}

	openTaskRecord, err := sm.Persistence.GetRaw(ctx, "Task", openTaskID)
	if err != nil {
		t.Fatalf("GetRaw(open Task): %v", err)
	}
	if got, ok := openTaskRecord["IsClosed"].(bool); !ok || got {
		t.Fatalf("open Task IsClosed = %#v, want false derived from Status=Not Started", openTaskRecord["IsClosed"])
	}

	closedTaskRecord, err := sm.Persistence.GetRaw(ctx, "Task", closedTaskID)
	if err != nil {
		t.Fatalf("GetRaw(closed Task): %v", err)
	}
	if got, ok := closedTaskRecord["IsClosed"].(bool); !ok || !got {
		t.Fatalf("closed Task IsClosed = %#v, want true derived from Status=Completed", closedTaskRecord["IsClosed"])
	}

	flowCtx := constants.WithStandardActionExecutionRootFlow(ctx, "sa_issue823_convert", "Std_ConvertLead")
	flowCtx = automation.WithTransientFlowInterviews(flowCtx)
	result, err := sm.FlowExecutor.ExecuteFlow(flowCtx, "Std_ConvertLead", map[string]interface{}{
		"leadId":                 leadID,
		"convertedStatus":        "Converted",
		"doNotCreateOpportunity": true,
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) error = %v", err)
	}
	if result == nil || !result.IsSuccess {
		t.Fatalf("ExecuteFlow(Std_ConvertLead) result = %#v, want success", result)
	}

	contactID, _ := result.OutputValues["contactId"].(string)
	if contactID == "" {
		t.Fatalf("contactId output = %q, want non-empty", contactID)
	}

	openTaskRecord, err = sm.Persistence.GetRaw(ctx, "Task", openTaskID)
	if err != nil {
		t.Fatalf("GetRaw(open Task after convert): %v", err)
	}
	if got, _ := openTaskRecord["WhoId"].(string); got != contactID {
		t.Fatalf("open Task WhoId after convert = %q, want converted Contact %q", got, contactID)
	}
	if got, ok := openTaskRecord["IsClosed"].(bool); !ok || got {
		t.Fatalf("open Task IsClosed after convert = %#v, want false", openTaskRecord["IsClosed"])
	}

	closedTaskRecord, err = sm.Persistence.GetRaw(ctx, "Task", closedTaskID)
	if err != nil {
		t.Fatalf("GetRaw(closed Task after convert): %v", err)
	}
	if got, _ := closedTaskRecord["WhoId"].(string); got != leadID {
		t.Fatalf("closed Task WhoId after convert = %q, want original Lead %q", got, leadID)
	}
	if got, ok := closedTaskRecord["IsClosed"].(bool); !ok || !got {
		t.Fatalf("closed Task IsClosed after convert = %#v, want true", closedTaskRecord["IsClosed"])
	}
}

// leadBeforeSaveConvertedAccountIdFlowMetadata returns a before-save flow that
// looks up the Account by $Record.ConvertedAccountId and writes "ANCHOR_VISIBLE"
// or "ANCHOR_MISSING" to $Record.Company depending on whether the lookup finds a row.
func leadBeforeSaveConvertedAccountIdFlowMetadata() models.FlowMetadata {
	return models.FlowMetadata{
		Start: &models.FlowStart{
			TriggerType:       "RecordBeforeSave",
			RecordTriggerType: "Update",
			Object:            "Lead",
			Connector:         &models.FlowConnector{TargetReference: "LookupConvertedAccount"},
		},
		RecordLookups: []models.FlowRecordLookup{
			{
				Name:   "LookupConvertedAccount",
				Label:  "Lookup Account via ConvertedAccountId",
				Object: "Account",
				Filters: []models.FlowRecordFilter{
					{Field: "Id", Operator: "EqualTo", Value: map[string]interface{}{"elementReference": "$Record.ConvertedAccountId"}},
				},
				OutputReference:    "$lookedAccount",
				GetFirstRecordOnly: true,
				Connector:          &models.FlowConnector{TargetReference: "CheckAccountFound"},
			},
		},
		Decisions: []models.FlowDecision{
			{
				Name:  "CheckAccountFound",
				Label: "Was the account found via ConvertedAccountId?",
				Rules: []models.FlowDecisionRule{
					{
						Name:           "AccountFound",
						ConditionLogic: "and",
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$lookedAccount.Id", Operator: "IsNotBlank"},
						},
						Connector: &models.FlowConnector{TargetReference: "MarkAnchorVisible"},
					},
				},
				DefaultConnector: &models.FlowConnector{TargetReference: "MarkAnchorMissing"},
			},
		},
		Assignments: []models.FlowAssignment{
			{
				Name:  "MarkAnchorVisible",
				Label: "Anchor was visible",
				Items: []models.FlowAssignmentItem{
					{AssignToReference: "$Record.Company", Operator: "Assign", Value: "ANCHOR_VISIBLE"},
				},
			},
			{
				Name:  "MarkAnchorMissing",
				Label: "Anchor was missing (before-save fired before anchors were known)",
				Items: []models.FlowAssignmentItem{
					{AssignToReference: "$Record.Company", Operator: "Assign", Value: "ANCHOR_MISSING"},
				},
			},
		},
	}
}

func leadBeforeSaveConvertedOpportunityIdFlowMetadata() models.FlowMetadata {
	return models.FlowMetadata{
		Start: &models.FlowStart{
			TriggerType:       "RecordBeforeSave",
			RecordTriggerType: "Update",
			Object:            "Lead",
			Connector:         &models.FlowConnector{TargetReference: "LookupConvertedOpportunity"},
		},
		RecordLookups: []models.FlowRecordLookup{
			{
				Name:   "LookupConvertedOpportunity",
				Label:  "Lookup Opportunity via ConvertedOpportunityId",
				Object: "Opportunity",
				Filters: []models.FlowRecordFilter{
					{Field: "Id", Operator: "EqualTo", Value: map[string]interface{}{"elementReference": "$Record.ConvertedOpportunityId"}},
				},
				OutputReference:    "$lookedOpportunity",
				GetFirstRecordOnly: true,
				Connector:          &models.FlowConnector{TargetReference: "CheckOpportunityFound"},
			},
		},
		Decisions: []models.FlowDecision{
			{
				Name:  "CheckOpportunityFound",
				Label: "Was the opportunity found via ConvertedOpportunityId?",
				Rules: []models.FlowDecisionRule{
					{
						Name:           "OpportunityFound",
						ConditionLogic: "and",
						Conditions: []models.FlowCondition{
							{LeftValueReference: "$lookedOpportunity.Id", Operator: "IsNotBlank"},
						},
						Connector: &models.FlowConnector{TargetReference: "MarkOpportunityVisible"},
					},
				},
				DefaultConnector: &models.FlowConnector{TargetReference: "MarkOpportunityMissing"},
			},
		},
		Assignments: []models.FlowAssignment{
			{
				Name:  "MarkOpportunityVisible",
				Label: "Opportunity was visible",
				Items: []models.FlowAssignmentItem{
					{AssignToReference: "$Record.Company", Operator: "Assign", Value: "OPPORTUNITY_VISIBLE"},
				},
			},
			{
				Name:  "MarkOpportunityMissing",
				Label: "Opportunity was missing",
				Items: []models.FlowAssignmentItem{
					{AssignToReference: "$Record.Company", Operator: "Assign", Value: "OPPORTUNITY_MISSING"},
				},
			},
		},
	}
}
