package automation

// executor_permission_formula_test.go — regression tests for $Permission hydration
// in flow formula consumers.
//
// Covers:
//   - Issue #609: super-user $Permission injection via InjectSuperUserPermissions.
//   - Issue #1733: normal-user $Permission hydration via PermissionProvider
//     (on-demand Flow Formula Resources and evaluateFilterFormulaGate).
//
// The approval_service and action_service consumers are covered by their own
// test suites; the shared formula.InjectSuperUserPermissions helper has dedicated
// unit tests in backend/pkg/formula/user_context_permission_test.go.

import (
	"context"
	"testing"

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

func resolveFlowFormulaForTest(t *testing.T, executor *FlowExecutor, ctx context.Context, interview *models.FlowInterview, flowMeta *models.FlowMetadata, user *models.UserSession, name string) interface{} {
	t.Helper()
	ctx = withFlowFormulaResourceScope(ctx, interview, flowMeta.Formulas)
	value, present, err := executor.ResolveFlowResource(ctx, interview, user, map[string]interface{}{"elementReference": name})
	if err != nil {
		t.Fatalf("ResolveFlowResource(%q): %v", name, err)
	}
	if !present {
		t.Fatalf("ResolveFlowResource(%q) present=false", name)
	}
	if _, persisted := interview.ContextData[name]; persisted {
		t.Fatalf("Formula Resource %q was persisted in ContextData", name)
	}
	return value
}

// fakePermissionProvider is a test double for PermissionProvider that returns
// a fixed set of system and custom permissions.
type fakePermissionProvider struct {
	systemPerms map[string]bool
	customPerms map[string]bool
}

func (f *fakePermissionProvider) GetEffectiveSystemPermissions(_ context.Context, _, _ string) (map[string]bool, error) {
	return f.systemPerms, nil
}

func (f *fakePermissionProvider) GetEffectiveCustomPermissions(_ context.Context, _, _ string) (map[string]bool, error) {
	return f.customPerms, nil
}

// buildSuperUserSession returns a UserSession with an arbitrary non-CRM profile;
// tests grant the super-user capability through the fake PermissionProvider.
func buildSuperUserSession() *models.UserSession {
	return &models.UserSession{
		ID:          "super-001",
		Name:        "Super Admin",
		ProfileID:   "jira_admin",
		ProfileName: "Jira Admin",
	}
}

func superUserFormulaExecutor() *FlowExecutor {
	return &FlowExecutor{
		permissions: &fakePermissionProvider{
			systemPerms: map[string]bool{string(constants.SuperUserCapability): true},
		},
	}
}

// TestFlowFormulaResource_SuperUser_PermissionKeyInjected verifies that when a
// super-user runs a flow whose FlowFormula resource references $Permission.X,
// the formula engine resolves $Permission.X == true rather than false.
// Regression guard for Issue #609 (super-user $Permission always returned
// false before the fix).
func TestFlowFormulaResource_SuperUser_PermissionKeyInjected(t *testing.T) {
	executor := superUserFormulaExecutor()
	user := buildSuperUserSession()

	interview := &models.FlowInterview{
		ContextData: map[string]interface{}{},
	}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{
				Name:       "HasCustomPerm",
				DataType:   "Boolean",
				Expression: "$Permission.CustomPerm",
			},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "HasCustomPerm")
	if got != true {
		t.Errorf("HasCustomPerm = %v (%T); want true for super-user $Permission.CustomPerm", got, got)
	}
}

// TestFlowFormulaResource_NonSuperUser_NoProvider_PermissionFalse verifies that
// a non-super-user with no PermissionProvider wired gets __Permission resolved as
// false (the safe empty-map default) rather than panicking or returning true.
func TestFlowFormulaResource_NonSuperUser_NoProvider_PermissionFalse(t *testing.T) {
	executor := &FlowExecutor{}

	user := &models.UserSession{
		ID:          "user-002",
		Name:        "Standard User",
		ProfileID:   constants.ProfileStandardUser,
		ProfileName: "Standard User",
	}

	interview := &models.FlowInterview{
		ContextData: map[string]interface{}{},
	}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{
				Name:       "HasCustomPerm",
				DataType:   "Boolean",
				Expression: "$Permission.CustomPerm",
			},
		},
	}

	// Without a PermissionProvider __Permission is an empty map, so
	// $Permission.CustomPerm resolves to false. Must NOT return true
	// (that would indicate super-user injection leaked) and must NOT panic.
	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "HasCustomPerm")
	if got == true {
		t.Errorf("HasCustomPerm = true for non-super-user with no PermissionProvider; want false")
	}
}

// TestFlowFormulaResource_NonSuperUser_WithProvider_PermissionTrue verifies that
// a non-super-user whose PermissionProvider grants CustomPerm gets the formula
// $Permission.CustomPerm resolved as true.  Regression guard for Issue #1733.
func TestFlowFormulaResource_NonSuperUser_WithProvider_PermissionTrue(t *testing.T) {
	executor := &FlowExecutor{
		permissions: &fakePermissionProvider{
			systemPerms: map[string]bool{},
			customPerms: map[string]bool{"CustomPerm": true},
		},
	}

	user := &models.UserSession{
		ID:          "user-003",
		Name:        "Standard User",
		ProfileID:   constants.ProfileStandardUser,
		ProfileName: "Standard User",
	}

	interview := &models.FlowInterview{
		ContextData: map[string]interface{}{},
	}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{
				Name:       "HasCustomPerm",
				DataType:   "Boolean",
				Expression: "$Permission.CustomPerm",
			},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "HasCustomPerm")
	if got != true {
		t.Errorf("HasCustomPerm = %v (%T); want true for normal-user with CustomPerm granted", got, got)
	}
}

// TestEvaluateFilterFormulaGate_NonSuperUser_WithProvider_PermissionTrue verifies that
// evaluateFilterFormulaGate hydrates __Permission for normal users via the
// PermissionProvider, so $Permission gates work correctly for non-super-users.
func TestEvaluateFilterFormulaGate_NonSuperUser_WithProvider_PermissionTrue(t *testing.T) {
	executor := &FlowExecutor{
		permissions: &fakePermissionProvider{
			systemPerms: map[string]bool{},
			customPerms: map[string]bool{"CanAccess": true},
		},
	}

	user := &models.UserSession{
		ID:          "user-004",
		Name:        "Standard User",
		ProfileID:   constants.ProfileStandardUser,
		ProfileName: "Standard User",
	}

	ctx := context.Background()
	contextData := map[string]interface{}{
		"$Record": models.SObject{"Id": "rec-001"},
	}

	// filterFormula that gates on $Permission.CanAccess
	result := executor.evaluateFilterFormulaGate(ctx, "Account", "RecordBeforeSave", "$Permission.CanAccess", contextData, user)
	if !result {
		t.Error("evaluateFilterFormulaGate returned false for normal-user with CanAccess granted; want true")
	}
}

// TestEvaluateFilterFormulaGate_NonSuperUser_NoProvider_PermissionFalse verifies that
// evaluateFilterFormulaGate fails closed (returns false) when no PermissionProvider
// is wired, so $Permission.X evaluates to false for normal users.
func TestEvaluateFilterFormulaGate_NonSuperUser_NoProvider_PermissionFalse(t *testing.T) {
	executor := &FlowExecutor{}

	user := &models.UserSession{
		ID:          "user-005",
		Name:        "Standard User",
		ProfileID:   constants.ProfileStandardUser,
		ProfileName: "Standard User",
	}

	ctx := context.Background()
	contextData := map[string]interface{}{
		"$Record": models.SObject{"Id": "rec-002"},
	}

	result := executor.evaluateFilterFormulaGate(ctx, "Account", "RecordBeforeSave", "$Permission.CanAccess", contextData, user)
	if result {
		t.Error("evaluateFilterFormulaGate returned true for normal-user with no PermissionProvider; want false (fail-closed)")
	}
}

// TestEvaluateFilterFormulaGate_SystemUser_PermissionTrue verifies that scheduled /
// system-automation runs (newSystemAutomationUser: no ProfileID, not super-user) get
// $Permission.X = true WITHOUT a PermissionProvider, mirroring the persistence-layer
// all-access of SystemContext. Before the fix they hydrated an empty map (no ProfileID)
// and $Permission.X was always false, silently blocking supported scheduled gates.
func TestEvaluateFilterFormulaGate_SystemUser_PermissionTrue(t *testing.T) {
	// No PermissionProvider wired — a normal user would fail closed here; the system
	// user must still resolve $Permission.X to true (system context = all-access).
	executor := &FlowExecutor{}

	user := newSystemAutomationUser()

	ctx := context.Background()
	contextData := map[string]interface{}{
		"$Record": models.SObject{"Id": "rec-sys-001"},
	}

	result := executor.evaluateFilterFormulaGate(ctx, "Account", "RecordBeforeSave", "$Permission.RunNightly", contextData, user)
	if !result {
		t.Error("evaluateFilterFormulaGate returned false for system-automation user with no provider; want true ($Permission must be all-access in system context)")
	}
}

// TestEvaluateFlowFormulas_SuperUser_MultiplePermissions verifies that all
// $Permission.X keys referenced in a single formula expression are injected as
// true for super-users — not just the first one.
func TestFlowFormulaResource_SuperUser_MultiplePermissions(t *testing.T) {
	executor := superUserFormulaExecutor()
	user := buildSuperUserSession()

	interview := &models.FlowInterview{
		ContextData: map[string]interface{}{},
	}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{
				Name:       "BothPerms",
				DataType:   "Boolean",
				Expression: "AND($Permission.PermA, $Permission.PermB)",
			},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "BothPerms")
	if got != true {
		t.Errorf("BothPerms = %v; want true for super-user with both $Permission keys", got)
	}
}

// ── MATRIX A: {super, system, normal} × {single, multi-perm-in-f2, multi-no-perm-in-f1} ──
//
// Row super×single is already covered by TestEvaluateFlowFormulas_SuperUser_PermissionKeyInjected.
// Row normal×single is covered by TestEvaluateFlowFormulas_NonSuperUser_WithProvider_PermissionTrue.
// The six new cells below complete the matrix.

// TestMatrix_Super_MultiPermInF2 — super × multi-formula with $Permission.X only in formula #2.
// Regression for grantsAllPerms per-formula top-up: without the fix, formula #2's key is not
// injected because InjectSuperUserPermissions is only called for keys present in firstExpr.
func TestMatrix_Super_MultiPermInF2(t *testing.T) {
	executor := superUserFormulaExecutor()
	user := buildSuperUserSession()

	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{Name: "NoPermRef", DataType: "Boolean", Expression: "1 = 1"},
			{Name: "HasPermF2", DataType: "Boolean", Expression: "$Permission.PermOnlyInF2"},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "HasPermF2")
	if got != true {
		t.Errorf("HasPermF2 = %v; want true: super-user $Permission.X in formula #2 must resolve true", got)
	}
}

// TestMatrix_Super_MultiNoPermInF1 — super × multi-formula where formula #1 has NO $Permission ref.
// Regression for __Permission initialisation: when firstExpr has no perm refs,
// InjectSuperUserPermissions returns early without setting __Permission. Formula #2 then
// sees an unset variable. The grantsAllPerms init block must pre-set the map.
func TestMatrix_Super_MultiNoPermInF1(t *testing.T) {
	executor := superUserFormulaExecutor()
	user := buildSuperUserSession()

	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{Name: "Const", DataType: "Boolean", Expression: "true"},
			{Name: "HasPerm", DataType: "Boolean", Expression: "$Permission.LateKey"},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "HasPerm")
	if got != true {
		t.Errorf("HasPerm = %v; want true: super-user $Permission.X in formula #2 (no perm in f1) must resolve true", got)
	}
}

// buildSystemUserSession returns the system-automation user (SystemUserID, no ProfileID).
func buildSystemUserSession() *models.UserSession {
	return newSystemAutomationUser()
}

// TestMatrix_System_Single — system × single formula with $Permission.X.
// Regression for #2047: system-automation user must resolve $Permission.X = true
// even without a PermissionProvider (system context = all-access).
func TestMatrix_System_Single(t *testing.T) {
	executor := &FlowExecutor{}
	user := buildSystemUserSession()

	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{Name: "SysPerm", DataType: "Boolean", Expression: "$Permission.SysGate"},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "SysPerm")
	if got != true {
		t.Errorf("SysPerm = %v; want true: system-automation user $Permission.X must resolve true", got)
	}
}

// TestMatrix_System_MultiPermInF2 — system × multi-formula with $Permission.X only in formula #2.
func TestMatrix_System_MultiPermInF2(t *testing.T) {
	executor := &FlowExecutor{}
	user := buildSystemUserSession()

	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{Name: "NoPermRef", DataType: "Boolean", Expression: "1 = 1"},
			{Name: "SysPermF2", DataType: "Boolean", Expression: "$Permission.SysOnlyInF2"},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "SysPermF2")
	if got != true {
		t.Errorf("SysPermF2 = %v; want true: system user $Permission.X in formula #2 must resolve true", got)
	}
}

// TestMatrix_System_MultiNoPermInF1 — system × multi-formula where formula #1 has NO $Permission ref.
func TestMatrix_System_MultiNoPermInF1(t *testing.T) {
	executor := &FlowExecutor{}
	user := buildSystemUserSession()

	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{Name: "Const", DataType: "Boolean", Expression: "true"},
			{Name: "SysPermLate", DataType: "Boolean", Expression: "$Permission.SysLate"},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "SysPermLate")
	if got != true {
		t.Errorf("SysPermLate = %v; want true: system user $Permission.X in formula #2 (no perm in f1) must resolve true", got)
	}
}

// TestMatrix_Normal_MultiPermInF2 — normal × multi-formula with $Permission.X only in formula #2.
// Provider grants PermInF2; formula #1 has no perm ref.
func TestMatrix_Normal_MultiPermInF2(t *testing.T) {
	executor := &FlowExecutor{
		permissions: &fakePermissionProvider{
			systemPerms: map[string]bool{},
			customPerms: map[string]bool{"PermInF2": true},
		},
	}
	user := &models.UserSession{
		ID:          "user-norm-multi",
		Name:        "Normal User",
		ProfileID:   constants.ProfileStandardUser,
		ProfileName: "Standard User",
	}

	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{Name: "NoPermRef", DataType: "Boolean", Expression: "1 = 1"},
			{Name: "NormPermF2", DataType: "Boolean", Expression: "$Permission.PermInF2"},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "NormPermF2")
	if got != true {
		t.Errorf("NormPermF2 = %v; want true: normal-user PermInF2 granted and referenced in formula #2", got)
	}
}

// TestMatrix_Normal_MultiNoPermInF1 — normal × multi-formula where formula #1 has NO $Permission ref.
// The __Permission map is hydrated by injectFormulaPermissions (normal path) regardless; formula #2
// must still resolve correctly.
func TestMatrix_Normal_MultiNoPermInF1(t *testing.T) {
	executor := &FlowExecutor{
		permissions: &fakePermissionProvider{
			systemPerms: map[string]bool{},
			customPerms: map[string]bool{"LateNormPerm": true},
		},
	}
	user := &models.UserSession{
		ID:          "user-norm-late",
		Name:        "Normal User",
		ProfileID:   constants.ProfileStandardUser,
		ProfileName: "Standard User",
	}

	interview := &models.FlowInterview{ContextData: map[string]interface{}{}}
	flowMeta := &models.FlowMetadata{
		Formulas: []models.FlowFormula{
			{Name: "Const", DataType: "Boolean", Expression: "true"},
			{Name: "NormPermLate", DataType: "Boolean", Expression: "$Permission.LateNormPerm"},
		},
	}

	got := resolveFlowFormulaForTest(t, executor, context.Background(), interview, flowMeta, user, "NormPermLate")
	if got != true {
		t.Errorf("NormPermLate = %v; want true: normal-user LateNormPerm granted, referenced in formula #2 with no perm in f1", got)
	}
}
