package services

import (
	"testing"
	"time"

	"github.com/breakpilot/consent-service/internal/models"
	"github.com/google/uuid"
)

// TestHashPassword tests password hashing
func TestHashPassword(t *testing.T) {
	// Create service without DB for unit tests
	s := &AuthService{}

	password := "testPassword123!"
	hash, err := s.HashPassword(password)

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

	if hash == "" {
		t.Error("Hash should not be empty")
	}

	if hash == password {
		t.Error("Hash should not equal the original password")
	}

	// Hash should be different each time (bcrypt uses random salt)
	hash2, _ := s.HashPassword(password)
	if hash == hash2 {
		t.Error("Same password should produce different hashes due to salt")
	}
}

// TestVerifyPassword tests password verification
func TestVerifyPassword(t *testing.T) {
	s := &AuthService{}

	password := "testPassword123!"
	hash, _ := s.HashPassword(password)

	// Should verify correct password
	if !s.VerifyPassword(password, hash) {
		t.Error("VerifyPassword should return true for correct password")
	}

	// Should reject incorrect password
	if s.VerifyPassword("wrongPassword", hash) {
		t.Error("VerifyPassword should return false for incorrect password")
	}

	// Should reject empty password
	if s.VerifyPassword("", hash) {
		t.Error("VerifyPassword should return false for empty password")
	}
}

// TestGenerateSecureToken tests token generation
func TestGenerateSecureToken(t *testing.T) {
	s := &AuthService{}

	tests := []struct {
		name   string
		length int
	}{
		{"short token", 16},
		{"standard token", 32},
		{"long token", 64},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			token, err := s.GenerateSecureToken(tt.length)
			if err != nil {
				t.Fatalf("GenerateSecureToken failed: %v", err)
			}

			if token == "" {
				t.Error("Token should not be empty")
			}

			// Tokens should be unique
			token2, _ := s.GenerateSecureToken(tt.length)
			if token == token2 {
				t.Error("Generated tokens should be unique")
			}
		})
	}
}

// TestHashToken tests token hashing for storage
func TestHashToken(t *testing.T) {
	s := &AuthService{}

	token := "test-token-123"
	hash := s.HashToken(token)

	if hash == "" {
		t.Error("Hash should not be empty")
	}

	if hash == token {
		t.Error("Hash should not equal the original token")
	}

	// Same token should produce same hash (deterministic)
	hash2 := s.HashToken(token)
	if hash != hash2 {
		t.Error("Same token should produce same hash")
	}

	// Different tokens should produce different hashes
	differentHash := s.HashToken("different-token")
	if hash == differentHash {
		t.Error("Different tokens should produce different hashes")
	}
}

// TestGenerateAccessToken tests JWT access token generation
func TestGenerateAccessToken(t *testing.T) {
	s := &AuthService{
		jwtSecret:      "test-secret-key-for-testing-purposes",
		accessTokenExp: time.Hour,
	}

	user := &models.User{
		ID:            uuid.New(),
		Email:         "test@example.com",
		Role:          "user",
		AccountStatus: "active",
	}

	token, err := s.GenerateAccessToken(user)
	if err != nil {
		t.Fatalf("GenerateAccessToken failed: %v", err)
	}

	if token == "" {
		t.Error("Token should not be empty")
	}

	// Token should have three parts (header.payload.signature)
	parts := 0
	for _, c := range token {
		if c == '.' {
			parts++
		}
	}
	if parts != 2 {
		t.Errorf("JWT token should have 3 parts, got %d dots", parts)
	}
}

// TestValidateAccessToken tests JWT token validation
func TestValidateAccessToken(t *testing.T) {
	secret := "test-secret-key-for-testing-purposes"
	s := &AuthService{
		jwtSecret:      secret,
		accessTokenExp: time.Hour,
	}

	user := &models.User{
		ID:            uuid.New(),
		Email:         "test@example.com",
		Role:          "admin",
		AccountStatus: "active",
	}

	token, _ := s.GenerateAccessToken(user)

	// Should validate valid token
	claims, err := s.ValidateAccessToken(token)
	if err != nil {
		t.Fatalf("ValidateAccessToken failed: %v", err)
	}

	if claims.UserID != user.ID.String() {
		t.Errorf("Expected UserID %s, got %s", user.ID.String(), claims.UserID)
	}

	if claims.Email != user.Email {
		t.Errorf("Expected Email %s, got %s", user.Email, claims.Email)
	}

	if claims.Role != user.Role {
		t.Errorf("Expected Role %s, got %s", user.Role, claims.Role)
	}
}

// TestValidateAccessToken_Invalid tests invalid token scenarios
func TestValidateAccessToken_Invalid(t *testing.T) {
	s := &AuthService{
		jwtSecret:      "test-secret-key-for-testing-purposes",
		accessTokenExp: time.Hour,
	}

	tests := []struct {
		name  string
		token string
	}{
		{"empty token", ""},
		{"invalid format", "not-a-jwt-token"},
		{"invalid signature", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiMTIzIn0.invalidsignature"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := s.ValidateAccessToken(tt.token)
			if err == nil {
				t.Error("ValidateAccessToken should fail for invalid token")
			}
		})
	}
}

// TestValidateAccessToken_WrongSecret tests token with wrong secret
func TestValidateAccessToken_WrongSecret(t *testing.T) {
	s1 := &AuthService{
		jwtSecret:      "secret-one",
		accessTokenExp: time.Hour,
	}

	s2 := &AuthService{
		jwtSecret:      "secret-two",
		accessTokenExp: time.Hour,
	}

	user := &models.User{
		ID:            uuid.New(),
		Email:         "test@example.com",
		Role:          "user",
		AccountStatus: "active",
	}

	// Generate token with first secret
	token, _ := s1.GenerateAccessToken(user)

	// Try to validate with second secret (should fail)
	_, err := s2.ValidateAccessToken(token)
	if err == nil {
		t.Error("ValidateAccessToken should fail when using wrong secret")
	}
}

// TestGenerateRefreshToken tests refresh token generation
func TestGenerateRefreshToken(t *testing.T) {
	s := &AuthService{}

	token, hash, err := s.GenerateRefreshToken()
	if err != nil {
		t.Fatalf("GenerateRefreshToken failed: %v", err)
	}

	if token == "" {
		t.Error("Token should not be empty")
	}

	if hash == "" {
		t.Error("Hash should not be empty")
	}

	// Verify hash matches token
	expectedHash := s.HashToken(token)
	if hash != expectedHash {
		t.Error("Returned hash should match hashed token")
	}

	// Tokens should be unique
	token2, hash2, _ := s.GenerateRefreshToken()
	if token == token2 {
		t.Error("Generated tokens should be unique")
	}
	if hash == hash2 {
		t.Error("Generated hashes should be unique")
	}
}

// TestPasswordStrength tests various password scenarios
func TestPasswordStrength(t *testing.T) {
	s := &AuthService{}

	passwords := []struct {
		password string
		valid    bool
	}{
		{"short", true},           // bcrypt accepts any length
		{"12345678", true},        // numbers only
		{"password", true},        // letters only
		{"Pass123!", true},        // mixed
		{"", true},                // empty (bcrypt allows)
		{string(make([]byte, 72)), true},  // max bcrypt length
	}

	for _, p := range passwords {
		hash, err := s.HashPassword(p.password)
		if p.valid && err != nil {
			t.Errorf("HashPassword failed for valid password %q: %v", p.password, err)
		}
		if p.valid && !s.VerifyPassword(p.password, hash) {
			t.Errorf("VerifyPassword failed for password %q", p.password)
		}
	}
}

// BenchmarkHashPassword benchmarks password hashing
func BenchmarkHashPassword(b *testing.B) {
	s := &AuthService{}
	password := "testPassword123!"

	for i := 0; i < b.N; i++ {
		s.HashPassword(password)
	}
}

// BenchmarkVerifyPassword benchmarks password verification
func BenchmarkVerifyPassword(b *testing.B) {
	s := &AuthService{}
	password := "testPassword123!"
	hash, _ := s.HashPassword(password)

	for i := 0; i < b.N; i++ {
		s.VerifyPassword(password, hash)
	}
}

// BenchmarkGenerateAccessToken benchmarks JWT token generation
func BenchmarkGenerateAccessToken(b *testing.B) {
	s := &AuthService{
		jwtSecret:      "test-secret-key-for-testing-purposes",
		accessTokenExp: time.Hour,
	}

	user := &models.User{
		ID:            uuid.New(),
		Email:         "test@example.com",
		Role:          "user",
		AccountStatus: "active",
	}

	for i := 0; i < b.N; i++ {
		s.GenerateAccessToken(user)
	}
}

// BenchmarkValidateAccessToken benchmarks JWT token validation
func BenchmarkValidateAccessToken(b *testing.B) {
	s := &AuthService{
		jwtSecret:      "test-secret-key-for-testing-purposes",
		accessTokenExp: time.Hour,
	}

	user := &models.User{
		ID:            uuid.New(),
		Email:         "test@example.com",
		Role:          "user",
		AccountStatus: "active",
	}

	token, _ := s.GenerateAccessToken(user)

	for i := 0; i < b.N; i++ {
		s.ValidateAccessToken(token)
	}
}
