package auth

import (
	"strings"
	"testing"
)

func TestBcryptHash(t *testing.T) {
	tests := []struct {
		name     string
		password string
		wantErr  bool
	}{
		{"valid password", "password123", false},
		{"empty password", "", false}, // bcrypt allows empty
		{"long password", strings.Repeat("a", 50), false},
		{"too long password - bcrypt limit", strings.Repeat("a", 73), true}, // bcrypt returns error for >72 bytes
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			hash, err := BcryptHash(tt.password)
			if (err != nil) != tt.wantErr {
				t.Errorf("BcryptHash() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if !tt.wantErr && hash == "" {
				t.Error("BcryptHash() returned empty hash")
			}
			if !tt.wantErr && !strings.HasPrefix(hash, "$2") {
				t.Errorf("BcryptHash() hash should start with $2, got %s", hash[:3])
			}
		})
	}
}

func TestBcryptVerify(t *testing.T) {
	// First create a hash to test against
	hash, err := BcryptHash("correct-password")
	if err != nil {
		t.Fatalf("BcryptHash() error = %v", err)
	}

	tests := []struct {
		name     string
		hash     string
		password string
		want     bool
	}{
		{"correct password", hash, "correct-password", true},
		{"wrong password", hash, "wrong-password", false},
		{"empty password", hash, "", false},
		{"invalid hash", "invalid-hash", "password", false},
		{"empty hash", "", "password", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := BcryptVerify(tt.hash, tt.password); got != tt.want {
				t.Errorf("BcryptVerify() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestBcryptVerify_DifferentPasswords(t *testing.T) {
	hash1, _ := BcryptHash("password1")
	hash2, _ := BcryptHash("password2")

	// Each hash should only verify its own password
	if !BcryptVerify(hash1, "password1") {
		t.Error("hash1 should verify password1")
	}
	if BcryptVerify(hash1, "password2") {
		t.Error("hash1 should not verify password2")
	}
	if !BcryptVerify(hash2, "password2") {
		t.Error("hash2 should verify password2")
	}
	if BcryptVerify(hash2, "password1") {
		t.Error("hash2 should not verify password1")
	}
}

func TestPassword_Verify_Argon2id(t *testing.T) {
	p := NewPassword()

	hash, err := p.Hash("test-password")
	if err != nil {
		t.Fatalf("Hash() error = %v", err)
	}

	// Verify correct password
	if !p.Verify(hash, "test-password") {
		t.Error("Verify() should return true for correct password")
	}

	// Verify wrong password
	if p.Verify(hash, "wrong-password") {
		t.Error("Verify() should return false for wrong password")
	}
}

func TestPassword_Verify_Bcrypt(t *testing.T) {
	p := NewPassword()

	// Create bcrypt hash
	bcryptHash, err := BcryptHash("bcrypt-password")
	if err != nil {
		t.Fatalf("BcryptHash() error = %v", err)
	}

	// Verify using Argon2id password manager (should support bcrypt)
	if !p.Verify(bcryptHash, "bcrypt-password") {
		t.Error("Verify() should support bcrypt hashes")
	}

	if p.Verify(bcryptHash, "wrong-password") {
		t.Error("Verify() should return false for wrong bcrypt password")
	}
}

func TestPassword_Verify_InvalidFormat(t *testing.T) {
	p := NewPassword()

	tests := []struct {
		name string
		hash string
		want bool
	}{
		{"empty hash", "", false},
		{"invalid format", "invalid", false},
		{"wrong number of parts", "$argon2id$v=19$m=65536,t=3,p=4$abc", false},
		{"wrong algorithm", "$scrypt$v=19$m=65536,t=3,p=4$salt$hash", false},
		{"invalid params", "$argon2id$v=19$m=abc,t=3,p=4$salt$hash", false},
		{"invalid salt hex", "$argon2id$v=19$m=65536,t=3,p=4$ZZZZZZZZ$hash", false},
		{"invalid hash hex", "$argon2id$v=19$m=65536,t=3,p=4$salt$ZZZZZZZZ", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := p.Verify(tt.hash, "password"); got != tt.want {
				t.Errorf("Verify() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestPassword_Hash_DifferentSalts(t *testing.T) {
	p := NewPassword()

	hash1, err := p.Hash("same-password")
	if err != nil {
		t.Fatalf("Hash() error = %v", err)
	}

	hash2, err := p.Hash("same-password")
	if err != nil {
		t.Fatalf("Hash() error = %v", err)
	}

	// Two hashes of the same password should be different (different salts)
	if hash1 == hash2 {
		t.Error("Hash() should generate different hashes for same password (different salts)")
	}

	// But both should verify the same password
	if !p.Verify(hash1, "same-password") {
		t.Error("hash1 should verify same-password")
	}
	if !p.Verify(hash2, "same-password") {
		t.Error("hash2 should verify same-password")
	}
}

func TestPassword_HashAndVerify_SpecialCharacters(t *testing.T) {
	p := NewPassword()

	tests := []string{
		"p@ssw0rd!",
		"密码测试",
		"パスワード",
		"  spaces  ",
		"tab\ttab",
		"newline\nnewline",
		strings.Repeat("a", 100),
	}

	for _, password := range tests {
		t.Run("password_"+password, func(t *testing.T) {
			hash, err := p.Hash(password)
			if err != nil {
				t.Fatalf("Hash() error = %v", err)
			}

			if !p.Verify(hash, password) {
				t.Errorf("Verify() failed for password: %q", password)
			}
		})
	}
}

func TestVerifyPassword_Wrapper(t *testing.T) {
	// Test Argon2id hash
	argonHash, err := HashPassword("argon-password")
	if err != nil {
		t.Fatalf("HashPassword() error = %v", err)
	}

	if !VerifyPassword(argonHash, "argon-password") {
		t.Error("VerifyPassword() should verify Argon2id hash")
	}

	// Test bcrypt hash
	bcryptHash, err := BcryptHash("bcrypt-password")
	if err != nil {
		t.Fatalf("BcryptHash() error = %v", err)
	}

	if !VerifyPassword(bcryptHash, "bcrypt-password") {
		t.Error("VerifyPassword() should verify bcrypt hash")
	}
}

func TestHashPassword_Wrapper(t *testing.T) {
	hash, err := HashPassword("test-password")
	if err != nil {
		t.Fatalf("HashPassword() error = %v", err)
	}

	if !strings.HasPrefix(hash, "$argon2id$") {
		t.Errorf("HashPassword() should return argon2id hash, got: %s", hash)
	}
}
