package auth_test

import (
	"strings"
	"testing"

	"backend/internal/auth"
)

// TestPassword_HashVerify verifies that Hash returns a valid PHC string
// starting with the expected argon2id prefix and that Verify confirms the
// original password.
func TestPassword_HashVerify(t *testing.T) {
	const pw = "correct-horse-battery-staple"
	phc, err := auth.Hash(pw, auth.TestParams)
	if err != nil {
		t.Fatalf("Hash returned error: %v", err)
	}
	if phc == "" {
		t.Fatal("Hash returned empty string")
	}

	// TestParams: Memory=8192, Iterations=1, Parallelism=2
	const wantPrefix = "$argon2id$v=19$m=8192,t=1,p=2$"
	if !strings.HasPrefix(phc, wantPrefix) {
		t.Errorf("PHC string does not start with expected prefix\n  got:  %s\n  want prefix: %s", phc, wantPrefix)
	}

	ok, err := auth.Verify(phc, pw)
	if err != nil {
		t.Fatalf("Verify returned unexpected error: %v", err)
	}
	if !ok {
		t.Fatal("Verify returned false for the original password")
	}
}

// TestPassword_VerifyWrong ensures that Verify returns (false, nil) — not an
// error — when the supplied password does not match the hash.
func TestPassword_VerifyWrong(t *testing.T) {
	const pw = "correct-horse-battery-staple"
	phc, err := auth.Hash(pw, auth.TestParams)
	if err != nil {
		t.Fatalf("Hash returned error: %v", err)
	}

	ok, err := auth.Verify(phc, pw+"x")
	if err != nil {
		t.Fatalf("Verify returned an unexpected error on wrong password: %v", err)
	}
	if ok {
		t.Fatal("Verify returned true for a wrong password")
	}
}

// TestPassword_VerifyMalformed checks that malformed or foreign-algorithm PHC
// strings return (false, ErrInvalidHash).
func TestPassword_VerifyMalformed(t *testing.T) {
	cases := []struct {
		name    string
		encoded string
	}{
		{"plain string", "not-a-phc-string"},
		{"too few segments", "$argon2id$v=19$m=8192,t=1,p=2$only-three-segments"},
		{"wrong algorithm", "$bcrypt$v=19$m=8192,t=1,p=2$saltb64$hashb64"},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			ok, err := auth.Verify(tc.encoded, "anypassword")
			if ok {
				t.Error("Verify returned true for malformed input")
			}
			if err != auth.ErrInvalidHash {
				t.Errorf("Verify error = %v, want ErrInvalidHash", err)
			}
		})
	}
}

// TestPassword_VerifyVersion ensures that a synthetic PHC string with v=18
// (one less than argon2.Version == 19) returns (false, ErrIncompatibleVersion).
func TestPassword_VerifyVersion(t *testing.T) {
	// Craft a syntactically valid PHC string with an old version number.
	// The segment count must be correct (6 after split on "$"):
	// $argon2id$v=18$m=8192,t=1,p=2$<validb64salt>$<validb64hash>
	synthetic := "$argon2id$v=18$m=8192,t=1,p=2$c29tZXNhbHQxNjBiYXNl$c29tZWhhc2gzMmJ5dGVzMDAwMDAwMDAwMDAwMDA"

	ok, err := auth.Verify(synthetic, "anypassword")
	if ok {
		t.Error("Verify returned true for incompatible version")
	}
	if err != auth.ErrIncompatibleVersion {
		t.Errorf("Verify error = %v, want ErrIncompatibleVersion", err)
	}
}

// TestPassword_DistinctSaltsPerCall ensures that two Hash calls for the same
// password produce different PHC strings (random salt per call — Pitfall guard).
func TestPassword_DistinctSaltsPerCall(t *testing.T) {
	const pw = "correct-horse-battery-staple"
	phc1, err := auth.Hash(pw, auth.TestParams)
	if err != nil {
		t.Fatalf("first Hash returned error: %v", err)
	}
	phc2, err := auth.Hash(pw, auth.TestParams)
	if err != nil {
		t.Fatalf("second Hash returned error: %v", err)
	}
	if phc1 == phc2 {
		t.Error("Two Hash calls for the same password returned identical PHC strings (salt reuse detected)")
	}
}

// TestPassword_DefaultParamsShape guards against accidental drift in DefaultParams.
// Values must match OWASP 2024 baseline as locked in D-08.
func TestPassword_DefaultParamsShape(t *testing.T) {
	p := auth.DefaultParams
	if p.Memory != 64*1024 {
		t.Errorf("DefaultParams.Memory = %d, want %d", p.Memory, 64*1024)
	}
	if p.Iterations != 1 {
		t.Errorf("DefaultParams.Iterations = %d, want 1", p.Iterations)
	}
	if p.Parallelism != 4 {
		t.Errorf("DefaultParams.Parallelism = %d, want 4", p.Parallelism)
	}
	if p.SaltLength != 16 {
		t.Errorf("DefaultParams.SaltLength = %d, want 16", p.SaltLength)
	}
	if p.KeyLength != 32 {
		t.Errorf("DefaultParams.KeyLength = %d, want 32", p.KeyLength)
	}
}
