package records

import (
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/auth"
	"github.com/nexuscrm/shared/pkg/constants"
)

// TestApplyUserDefaults_MissingPasswordStampsUnguessablePlaceholder locks the
// Salesforce v63 parity contract for POST /sobjects/User: when the caller
// omits Password, the insert still succeeds (the row reaches SQL with a
// non-NULL bcrypt placeholder) and the user cannot authenticate until the
// admin runs the existing reset flow. Issue #1425.
func TestApplyUserDefaults_MissingPasswordStampsUnguessablePlaceholder(t *testing.T) {
	data := map[string]interface{}{
		constants.FieldEmail:     "diag@example.invalid",
		constants.FieldFirstName: "Diag",
		constants.FieldLastName:  "User",
	}
	if err := applyUserDefaults(data); err != nil {
		t.Fatalf("applyUserDefaults err = %v, want nil", err)
	}
	raw, ok := data[constants.FieldPassword].(string)
	if !ok || raw == "" {
		t.Fatalf("Password = %q, want non-empty bcrypt hash", raw)
	}
	if !strings.HasPrefix(raw, "$2") {
		t.Fatalf("Password = %q, want bcrypt-formatted ($2…)", raw)
	}
	// Placeholder must not verify against trivially-guessable inputs.
	for _, guess := range []string{"", "password", "Password1", "admin", "TempPassw0rd!"} {
		if auth.VerifyPassword(guess, raw) {
			t.Fatalf("placeholder bcrypt accepts guess %q — must be unguessable", guess)
		}
	}
}

// TestApplyUserDefaults_PlaceholdersAreUniquePerCall guards against any future
// regression that would seed the same placeholder for every no-Password
// insert (which would create a class of users sharing a verifiable hash).
func TestApplyUserDefaults_PlaceholdersAreUniquePerCall(t *testing.T) {
	a := map[string]interface{}{constants.FieldEmail: "a@example.invalid"}
	b := map[string]interface{}{constants.FieldEmail: "b@example.invalid"}
	if err := applyUserDefaults(a); err != nil {
		t.Fatalf("applyUserDefaults(a) err = %v", err)
	}
	if err := applyUserDefaults(b); err != nil {
		t.Fatalf("applyUserDefaults(b) err = %v", err)
	}
	if a[constants.FieldPassword] == b[constants.FieldPassword] {
		t.Fatalf("placeholders collided across calls — must be unique per row")
	}
}

// TestApplyUserDefaults_PlaintextPasswordIsHashed pins the existing contract
// for caller-supplied plaintext passwords. Pre-#1425 behavior must not
// regress under the new branch.
func TestApplyUserDefaults_PlaintextPasswordIsHashed(t *testing.T) {
	data := map[string]interface{}{
		constants.FieldEmail:    "caller@example.invalid",
		constants.FieldPassword: "S3cretCaller!",
	}
	if err := applyUserDefaults(data); err != nil {
		t.Fatalf("applyUserDefaults err = %v", err)
	}
	got, _ := data[constants.FieldPassword].(string)
	if !strings.HasPrefix(got, "$2") {
		t.Fatalf("Password = %q, want bcrypt-formatted", got)
	}
	if !auth.VerifyPassword("S3cretCaller!", got) {
		t.Fatalf("hashed result does not verify against the supplied plaintext")
	}
}

// TestApplyUserDefaults_PreHashedPasswordPassesThrough pins the existing
// contract for caller-supplied bcrypt hashes (e.g. migrated tenants):
// applyUserDefaults validates cost and leaves the hash unchanged.
func TestApplyUserDefaults_PreHashedPasswordPassesThrough(t *testing.T) {
	hashed, err := auth.HashPassword("pre-hashed-input")
	if err != nil {
		t.Fatalf("HashPassword setup err = %v", err)
	}
	data := map[string]interface{}{
		constants.FieldEmail:    "prehashed@example.invalid",
		constants.FieldPassword: hashed,
	}
	if err := applyUserDefaults(data); err != nil {
		t.Fatalf("applyUserDefaults err = %v", err)
	}
	if got := data[constants.FieldPassword]; got != hashed {
		t.Fatalf("pre-hashed Password mutated: got %v want %v", got, hashed)
	}
}

// TestApplyUserDefaults_UsernameDefaultsFromEmail keeps the older
// Username-from-Email contract intact alongside the new Password branch.
func TestApplyUserDefaults_UsernameDefaultsFromEmail(t *testing.T) {
	data := map[string]interface{}{
		constants.FieldEmail:    "unique@example.invalid",
		constants.FieldPassword: "S3cret!",
	}
	if err := applyUserDefaults(data); err != nil {
		t.Fatalf("applyUserDefaults err = %v", err)
	}
	if got := data[constants.FieldUsername]; got != "unique@example.invalid" {
		t.Fatalf("Username = %v, want defaulted from Email", got)
	}
}

// TestApplyUserDefaults_EmptyUsernameTreatedAsMissing guards a subtle
// regression introduced when this PR flipped Username.Required to 0: the
// handler-level validator no longer rejects an empty-string Username, so
// the defaulter must fill it from Email itself or the row dies at SQL
// (`Username VARCHAR(255) NOT NULL UNIQUE`). Pre-this-PR an empty
// Username was rejected with 400 at the validator edge.
func TestApplyUserDefaults_EmptyUsernameTreatedAsMissing(t *testing.T) {
	data := map[string]interface{}{
		constants.FieldUsername: "",
		constants.FieldEmail:    "empty-uname@example.invalid",
		constants.FieldPassword: "S3cret!",
	}
	if err := applyUserDefaults(data); err != nil {
		t.Fatalf("applyUserDefaults err = %v", err)
	}
	if got := data[constants.FieldUsername]; got != "empty-uname@example.invalid" {
		t.Fatalf("Username = %v, want defaulted from Email when empty", got)
	}
}

// TestApplyUserDefaults_DoesNotSynthesizeFirstNameWhenOmitted locks the
// storage-authority boundary for issue #974: create-contract relaxation must
// come from schema/validation, not from fabricating caller-authored name data.
func TestApplyUserDefaults_DoesNotSynthesizeFirstNameWhenOmitted(t *testing.T) {
	data := map[string]interface{}{
		constants.FieldEmail:    "lastname-only@example.invalid",
		constants.FieldLastName: "Doe",
		constants.FieldPassword: "S3cret!",
	}
	if err := applyUserDefaults(data); err != nil {
		t.Fatalf("applyUserDefaults err = %v, want nil", err)
	}
	if _, ok := data[constants.FieldFirstName]; ok {
		t.Fatalf("FirstName synthesized into payload: %v", data[constants.FieldFirstName])
	}
}

// TestApplyUserDefaults_FirstNameNotOverwritten when explicitly provided.
// Verifies the synthesis only fills in missing FirstName; callers who
// supply FirstName must keep their value.
func TestApplyUserDefaults_FirstNameNotOverwritten(t *testing.T) {
	data := map[string]interface{}{
		constants.FieldEmail:     "first-provided@example.invalid",
		constants.FieldFirstName: "Jane",
		constants.FieldLastName:  "Smith",
		constants.FieldPassword:  "S3cret!",
	}
	if err := applyUserDefaults(data); err != nil {
		t.Fatalf("applyUserDefaults err = %v", err)
	}
	firstName, ok := data[constants.FieldFirstName].(string)
	if !ok || firstName == "" {
		t.Fatalf("FirstName missing after applyUserDefaults")
	}
	if firstName != "Jane" {
		t.Fatalf("FirstName = %q, want \"Jane\" (caller-supplied must not be overwritten)", firstName)
	}
}
