package auth

import (
	"strings"
	"testing"
)

func TestGenerateAPIToken(t *testing.T) {
	token, err := GenerateAPIToken()
	if err != nil {
		t.Fatalf("GenerateAPIToken() error: %v", err)
	}

	// Should be 64 hex characters (32 bytes)
	if len(token) != 64 {
		t.Errorf("GenerateAPIToken() length = %d, want 64", len(token))
	}

	// Should be valid hex
	for _, c := range token {
		if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) {
			t.Errorf("GenerateAPIToken() contains invalid hex character: %c", c)
		}
	}

	// Should generate unique tokens
	token2, err := GenerateAPIToken()
	if err != nil {
		t.Fatalf("GenerateAPIToken() second call error: %v", err)
	}
	if token == token2 {
		t.Error("GenerateAPIToken() generated duplicate tokens")
	}
}

func TestHashAPIToken(t *testing.T) {
	token := "test-token-12345"
	hash := HashAPIToken(token)

	// Should be 64 hex characters (SHA3-256)
	if len(hash) != 64 {
		t.Errorf("HashAPIToken() length = %d, want 64", len(hash))
	}

	// Should be deterministic
	hash2 := HashAPIToken(token)
	if hash != hash2 {
		t.Error("HashAPIToken() is not deterministic")
	}

	// Different tokens should produce different hashes
	hash3 := HashAPIToken("different-token")
	if hash == hash3 {
		t.Error("HashAPIToken() produced same hash for different tokens")
	}
}

func TestCompareAPIToken(t *testing.T) {
	token := "my-secret-token-abc123"
	hash := HashAPIToken(token)

	// Correct token should match
	if !CompareAPIToken(token, hash) {
		t.Error("CompareAPIToken() returned false for correct token")
	}

	// Wrong token should not match
	if CompareAPIToken("wrong-token", hash) {
		t.Error("CompareAPIToken() returned true for wrong token")
	}

	// Empty token should not match
	if CompareAPIToken("", hash) {
		t.Error("CompareAPIToken() returned true for empty token")
	}

	// Token against wrong hash should not match
	if CompareAPIToken(token, "0000000000000000000000000000000000000000000000000000000000000000") {
		t.Error("CompareAPIToken() returned true for wrong hash")
	}
}

func TestCompareAPIToken_TimingSafe(t *testing.T) {
	// This test verifies the comparison uses constant-time comparison
	// by checking that it uses subtle.ConstantTimeCompare internally
	// (verified by code inspection - this test just ensures the function works)
	token := "timing-test-token"
	hash := HashAPIToken(token)

	// Multiple comparisons should all succeed
	for i := 0; i < 100; i++ {
		if !CompareAPIToken(token, hash) {
			t.Errorf("CompareAPIToken() failed on iteration %d", i)
		}
	}
}

func TestIsAPITokenHashed(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{
			name:     "valid 64-char hex",
			input:    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
			expected: true,
		},
		{
			name:     "actual hash",
			input:    HashAPIToken("test"),
			expected: true,
		},
		{
			name:     "too short",
			input:    "0123456789abcdef",
			expected: false,
		},
		{
			name:     "too long",
			input:    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef00",
			expected: false,
		},
		{
			name:     "invalid hex characters",
			input:    "ghijklmnopqrstuv0123456789abcdef0123456789abcdef0123456789abcdef",
			expected: false,
		},
		{
			name:     "empty string",
			input:    "",
			expected: false,
		},
		{
			name:     "plain token (not hashed)",
			input:    "my-api-token",
			expected: false,
		},
		{
			name:     "uppercase hex",
			input:    "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF",
			expected: true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := IsAPITokenHashed(tc.input)
			if result != tc.expected {
				t.Errorf("IsAPITokenHashed(%q) = %v, want %v", tc.input, result, tc.expected)
			}
		})
	}
}

func TestHashPassword(t *testing.T) {
	password := "mysecretpassword123"

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

	// Should be a bcrypt hash (starts with $2)
	if !strings.HasPrefix(hash, "$2") {
		t.Errorf("HashPassword() did not produce bcrypt hash, got: %s", hash[:10])
	}

	// Should be 60 characters
	if len(hash) != 60 {
		t.Errorf("HashPassword() length = %d, want 60", len(hash))
	}

	// Same password should produce different hashes (due to salt)
	hash2, err := HashPassword(password)
	if err != nil {
		t.Fatalf("HashPassword() second call error: %v", err)
	}
	if hash == hash2 {
		t.Error("HashPassword() produced same hash twice (missing salt?)")
	}
}

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

	// Correct password should verify
	if !CheckPasswordHash(password, hash) {
		t.Error("CheckPasswordHash() returned false for correct password")
	}

	// Wrong password should not verify
	if CheckPasswordHash("wrongpassword", hash) {
		t.Error("CheckPasswordHash() returned true for wrong password")
	}

	// Empty password should not verify
	if CheckPasswordHash("", hash) {
		t.Error("CheckPasswordHash() returned true for empty password")
	}
}

func TestIsPasswordHashed(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected bool
	}{
		{
			name:     "valid bcrypt hash",
			input:    "$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.qGAL0kcNWQKS5.",
			expected: true,
		},
		{
			name:     "bcrypt 2b variant",
			input:    "$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.qGAL0kcNWQKS5.",
			expected: true,
		},
		{
			name:     "bcrypt 2y variant",
			input:    "$2y$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.qGAL0kcNWQKS5.",
			expected: true,
		},
		{
			name:     "plain password",
			input:    "mypassword",
			expected: false,
		},
		{
			name:     "empty string",
			input:    "",
			expected: false,
		},
		{
			name:     "wrong length",
			input:    "$2a$12$short",
			expected: false,
		},
		{
			name:     "wrong prefix",
			input:    "$1a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.qGAL0kcNWQKS5.",
			expected: false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			result := IsPasswordHashed(tc.input)
			if result != tc.expected {
				t.Errorf("IsPasswordHashed(%q) = %v, want %v", tc.input, result, tc.expected)
			}
		})
	}
}

func TestValidatePasswordComplexity(t *testing.T) {
	tests := []struct {
		name      string
		password  string
		wantError bool
	}{
		{
			name:      "valid long password",
			password:  "thisisaverylongpassword",
			wantError: false,
		},
		{
			name:      "exactly minimum length",
			password:  "123456789012", // 12 chars
			wantError: false,
		},
		{
			name:      "too short",
			password:  "12345678901", // 11 chars
			wantError: true,
		},
		{
			name:      "empty password",
			password:  "",
			wantError: true,
		},
		{
			name:      "single character",
			password:  "a",
			wantError: true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			err := ValidatePasswordComplexity(tc.password)
			if tc.wantError && err == nil {
				t.Errorf("ValidatePasswordComplexity(%q) expected error, got nil", tc.password)
			}
			if !tc.wantError && err != nil {
				t.Errorf("ValidatePasswordComplexity(%q) unexpected error: %v", tc.password, err)
			}
		})
	}
}

func TestBcryptCost(t *testing.T) {
	// Verify bcrypt cost is reasonable (10-14 is typical)
	if BcryptCost < 10 || BcryptCost > 14 {
		t.Errorf("BcryptCost = %d, should be between 10 and 14 for reasonable security/performance", BcryptCost)
	}
}

func TestMinPasswordLength(t *testing.T) {
	// Verify minimum password length is reasonable (at least 8, ideally 12+)
	if MinPasswordLength < 8 {
		t.Errorf("MinPasswordLength = %d, should be at least 8", MinPasswordLength)
	}
}
