package formula

import (
	"testing"

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

func newUserSession() *models.UserSession {
	email := "alice@example.com"
	roleID := "role-007"
	roleName := "Senior Sales Rep"
	return &models.UserSession{
		ID:          "user-001",
		Name:        "Alice",
		Email:       &email,
		ProfileID:   "prof-001",
		ProfileName: "System Administrator",
		RoleID:      &roleID,
		RoleName:    &roleName,
	}
}

func TestBuildFormulaUserContext_NilUser(t *testing.T) {
	if got := BuildFormulaUserContext(nil); got != nil {
		t.Errorf("BuildFormulaUserContext(nil) = %v; want nil", got)
	}
}

func TestBuildFormulaUserContext_PopulatesAllGlobals(t *testing.T) {
	ctx := BuildFormulaUserContext(newUserSession())

	user, ok := ctx["__User"].(map[string]interface{})
	if !ok {
		t.Fatalf("__User missing or wrong type: %T", ctx["__User"])
	}
	if user["Id"] != "user-001" || user["ID"] != "user-001" {
		t.Errorf("__User.Id/ID = %v/%v; want user-001 (both)", user["Id"], user["ID"])
	}
	if user["Name"] != "Alice" {
		t.Errorf("__User.Name = %v; want Alice", user["Name"])
	}
	if user["Email"] != "alice@example.com" {
		t.Errorf("__User.Email = %v; want alice@example.com", user["Email"])
	}
	if user["ProfileId"] != "prof-001" {
		t.Errorf("__User.ProfileId = %v; want prof-001", user["ProfileId"])
	}
	if user["UserRoleId"] != "role-007" {
		t.Errorf("__User.UserRoleId = %v; want role-007", user["UserRoleId"])
	}

	prof, ok := ctx["__Profile"].(map[string]interface{})
	if !ok {
		t.Fatalf("__Profile missing or wrong type: %T", ctx["__Profile"])
	}
	if prof["Id"] != "prof-001" {
		t.Errorf("__Profile.Id = %v; want prof-001", prof["Id"])
	}
	if prof["Name"] != "System Administrator" {
		t.Errorf("__Profile.Name = %v; want System Administrator", prof["Name"])
	}

	role, ok := ctx["__UserRole"].(map[string]interface{})
	if !ok {
		t.Fatalf("__UserRole missing or wrong type: %T", ctx["__UserRole"])
	}
	if role["Id"] != "role-007" {
		t.Errorf("__UserRole.Id = %v; want role-007", role["Id"])
	}
	if role["Name"] != "Senior Sales Rep" {
		t.Errorf("__UserRole.Name = %v; want Senior Sales Rep", role["Name"])
	}
}

func TestBuildFormulaUserContext_NilEmail_NilRole(t *testing.T) {
	user := &models.UserSession{
		ID:          "user-002",
		Name:        "Bob",
		ProfileID:   "prof-002",
		ProfileName: "Standard User",
		// Email, RoleID, RoleName all nil
	}
	ctx := BuildFormulaUserContext(user)

	uMap := ctx["__User"].(map[string]interface{})
	if uMap["Email"] != nil {
		t.Errorf("__User.Email with nil session Email = %v; want nil", uMap["Email"])
	}
	if uMap["UserRoleId"] != nil {
		t.Errorf("__User.UserRoleId with nil RoleID = %v; want nil", uMap["UserRoleId"])
	}

	role := ctx["__UserRole"].(map[string]interface{})
	if role["Id"] != "" {
		t.Errorf("__UserRole.Id with nil RoleID = %v; want empty string fallback", role["Id"])
	}
	if role["Name"] != "" {
		t.Errorf("__UserRole.Name with nil RoleName = %v; want empty string fallback", role["Name"])
	}
}

func TestBuildFormulaUserContext_SyntheticActorUsesBehaviorProfileOnly(t *testing.T) {
	user := &models.UserSession{
		ID:   "system",
		Name: "Rollup Engine",
	}
	ctx := BuildFormulaUserContext(user)

	uMap := ctx["__User"].(map[string]interface{})
	if uMap["ProfileId"] != "" {
		t.Errorf("__User.ProfileId = %v; want empty profile for synthetic actor", uMap["ProfileId"])
	}
	profile := ctx["__Profile"].(map[string]interface{})
	if profile["Id"] != "" {
		t.Errorf("__Profile.Id = %v; want empty profile for synthetic actor", profile["Id"])
	}
	if profile["Name"] != "" {
		t.Errorf("__Profile.Name = %v; want empty profile for synthetic actor", profile["Name"])
	}
	if user.ProfileID != "" || user.ProfileName != "" || user.IsSystemAdmin {
		t.Errorf("behavior profile = (%q, %q, %v); want empty/false", user.ProfileID, user.ProfileName, user.IsSystemAdmin)
	}
}

// SSoT regression — single read path for $User/$Profile/$UserRole formula
// globals. This test asserts that any caller invoking BuildFormulaUserContext
// with an identical *UserSession gets byte-identical __User/__Profile/__UserRole
// projections — i.e. flow formulas and validation rules cannot diverge on the
// same merge field for the same session token. Regression guard for the codex
// review concern that a Profile rename mid-session could cause flow and
// validation to evaluate $Profile.Name against different facts.
func TestBuildFormulaUserContext_SingleReadPath_DeterministicProjection(t *testing.T) {
	user := newUserSession()

	pathA := BuildFormulaUserContext(user)
	pathB := BuildFormulaUserContext(user)

	for _, key := range []string{"__User", "__Profile", "__UserRole"} {
		mapA := pathA[key].(map[string]interface{})
		mapB := pathB[key].(map[string]interface{})
		if len(mapA) != len(mapB) {
			t.Errorf("%s len mismatch: pathA=%d pathB=%d", key, len(mapA), len(mapB))
		}
		for k, vA := range mapA {
			if vB, ok := mapB[k]; !ok || vA != vB {
				t.Errorf("%s.%s diverged: pathA=%v pathB=%v", key, k, vA, vB)
			}
		}
	}
}
