package services

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/rs/zerolog/log"
	"golang.org/x/crypto/bcrypt"

	"github.com/treytartt/honeydue-api/internal/apperrors"
	"github.com/treytartt/honeydue-api/internal/config"
	"github.com/treytartt/honeydue-api/internal/dto/requests"
	"github.com/treytartt/honeydue-api/internal/dto/responses"
	"github.com/treytartt/honeydue-api/internal/models"
	"github.com/treytartt/honeydue-api/internal/repositories"
)

// Deprecated: Legacy error constants - kept for reference during transition
// Use apperrors package instead
var (
	// ErrInvalidCredentials   = errors.New("invalid credentials")
	// ErrUsernameTaken        = errors.New("username already taken")
	// ErrEmailTaken           = errors.New("email already taken")
	// ErrUserInactive         = errors.New("user account is inactive")
	// ErrInvalidCode          = errors.New("invalid verification code")
	// ErrCodeExpired          = errors.New("verification code expired")
	// ErrAlreadyVerified      = errors.New("email already verified")
	// ErrRateLimitExceeded    = errors.New("too many requests, please try again later")
	// ErrInvalidResetToken    = errors.New("invalid or expired reset token")
	ErrAppleSignInFailed  = errors.New("Apple Sign In failed")
	ErrGoogleSignInFailed = errors.New("Google Sign In failed")
)

// AuthService handles authentication business logic
type AuthService struct {
	userRepo         *repositories.UserRepository
	notificationRepo *repositories.NotificationRepository
	cfg              *config.Config
}

// NewAuthService creates a new auth service
func NewAuthService(userRepo *repositories.UserRepository, cfg *config.Config) *AuthService {
	return &AuthService{
		userRepo: userRepo,
		cfg:      cfg,
	}
}

// SetNotificationRepository sets the notification repository for creating notification preferences
func (s *AuthService) SetNotificationRepository(notificationRepo *repositories.NotificationRepository) {
	s.notificationRepo = notificationRepo
}

// Login authenticates a user and returns a token
func (s *AuthService) Login(req *requests.LoginRequest) (*responses.LoginResponse, error) {
	// Find user by username or email
	identifier := req.Username
	if identifier == "" {
		identifier = req.Email
	}

	user, err := s.userRepo.FindByUsernameOrEmail(identifier)
	if err != nil {
		if errors.Is(err, repositories.ErrUserNotFound) {
			return nil, apperrors.Unauthorized("error.invalid_credentials")
		}
		return nil, apperrors.Internal(err)
	}

	// Check if user is active
	if !user.IsActive {
		return nil, apperrors.Unauthorized("error.account_inactive")
	}

	// Verify password
	if !user.CheckPassword(req.Password) {
		return nil, apperrors.Unauthorized("error.invalid_credentials")
	}

	// Get or create auth token
	token, err := s.userRepo.GetOrCreateToken(user.ID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// Update last login
	if err := s.userRepo.UpdateLastLogin(user.ID); err != nil {
		// Log error but don't fail the login
		log.Warn().Err(err).Uint("user_id", user.ID).Msg("Failed to update last login")
	}

	return &responses.LoginResponse{
		Token: token.Key,
		User:  responses.NewUserResponse(user),
	}, nil
}

// Register creates a new user account.
// F-10: User creation, profile creation, notification preferences, and confirmation code
// are wrapped in a transaction for atomicity.
func (s *AuthService) Register(req *requests.RegisterRequest) (*responses.RegisterResponse, string, error) {
	// Check if username exists
	exists, err := s.userRepo.ExistsByUsername(req.Username)
	if err != nil {
		return nil, "", apperrors.Internal(err)
	}
	if exists {
		return nil, "", apperrors.Conflict("error.username_taken")
	}

	// Check if email exists
	exists, err = s.userRepo.ExistsByEmail(req.Email)
	if err != nil {
		return nil, "", apperrors.Internal(err)
	}
	if exists {
		return nil, "", apperrors.Conflict("error.email_taken")
	}

	// Create user
	user := &models.User{
		Username:  req.Username,
		Email:     req.Email,
		FirstName: req.FirstName,
		LastName:  req.LastName,
		IsActive:  true,
	}

	// Hash password
	if err := user.SetPassword(req.Password); err != nil {
		return nil, "", apperrors.Internal(err)
	}

	// Generate confirmation code - use fixed code when DEBUG_FIXED_CODES is enabled for easier local testing
	var code string
	if s.cfg.Server.DebugFixedCodes {
		code = "123456"
	} else {
		code = generateSixDigitCode()
	}
	expiresAt := time.Now().UTC().Add(s.cfg.Security.ConfirmationExpiry)

	// Wrap user creation + profile + notification preferences + confirmation code in a transaction
	txErr := s.userRepo.Transaction(func(txRepo *repositories.UserRepository) error {
		// Save user
		if err := txRepo.Create(user); err != nil {
			return err
		}

		// Create user profile
		if _, err := txRepo.GetOrCreateProfile(user.ID); err != nil {
			log.Warn().Err(err).Uint("user_id", user.ID).Msg("Failed to create user profile during registration")
		}

		// Create notification preferences with all options enabled
		if s.notificationRepo != nil {
			if _, err := s.notificationRepo.GetOrCreatePreferences(user.ID); err != nil {
				log.Warn().Err(err).Uint("user_id", user.ID).Msg("Failed to create notification preferences during registration")
			}
		}

		// Create confirmation code
		if _, err := txRepo.CreateConfirmationCode(user.ID, code, expiresAt); err != nil {
			log.Warn().Err(err).Uint("user_id", user.ID).Msg("Failed to create confirmation code during registration")
		}

		return nil
	})
	if txErr != nil {
		return nil, "", apperrors.Internal(txErr)
	}

	// Create auth token (outside transaction since token generation is idempotent)
	token, err := s.userRepo.GetOrCreateToken(user.ID)
	if err != nil {
		return nil, "", apperrors.Internal(err)
	}

	return &responses.RegisterResponse{
		Token:   token.Key,
		User:    responses.NewUserResponse(user),
		Message: "Registration successful. Please check your email to verify your account.",
	}, code, nil
}

// RefreshToken handles token refresh logic.
// - If token is expired (> expiryDays old), returns error (must re-login).
// - If token is in the renewal window (> refreshDays old), generates a new token.
// - If token is still fresh (< refreshDays old), returns the existing token (no-op).
func (s *AuthService) RefreshToken(tokenKey string, userID uint) (*responses.RefreshTokenResponse, error) {
	expiryDays := s.cfg.Security.TokenExpiryDays
	if expiryDays <= 0 {
		expiryDays = 90
	}
	refreshDays := s.cfg.Security.TokenRefreshDays
	if refreshDays <= 0 {
		refreshDays = 60
	}

	// Look up the token
	authToken, err := s.userRepo.FindTokenByKey(tokenKey)
	if err != nil {
		return nil, apperrors.Unauthorized("error.invalid_token")
	}

	// Verify ownership
	if authToken.UserID != userID {
		return nil, apperrors.Unauthorized("error.invalid_token")
	}

	tokenAge := time.Since(authToken.Created)
	expiryDuration := time.Duration(expiryDays) * 24 * time.Hour
	refreshDuration := time.Duration(refreshDays) * 24 * time.Hour

	// Token is expired — must re-login
	if tokenAge > expiryDuration {
		return nil, apperrors.Unauthorized("error.token_expired")
	}

	// Token is still fresh — no-op refresh
	if tokenAge < refreshDuration {
		return &responses.RefreshTokenResponse{
			Token:   tokenKey,
			Message: "Token is still valid.",
		}, nil
	}

	// Token is in the renewal window — generate a new one
	// Delete the old token
	if err := s.userRepo.DeleteToken(tokenKey); err != nil {
		log.Warn().Err(err).Str("token", tokenKey[:8]+"...").Msg("Failed to delete old token during refresh")
	}

	// Create a new token
	newToken, err := s.userRepo.CreateToken(userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	return &responses.RefreshTokenResponse{
		Token:   newToken.Key,
		Message: "Token refreshed successfully.",
	}, nil
}

// Logout invalidates a user's token
func (s *AuthService) Logout(token string) error {
	return s.userRepo.DeleteToken(token)
}

// GetCurrentUser returns the current authenticated user with profile
func (s *AuthService) GetCurrentUser(userID uint) (*responses.CurrentUserResponse, error) {
	user, err := s.userRepo.FindByIDWithProfile(userID)
	if err != nil {
		return nil, err
	}

	authProvider, err := s.userRepo.FindAuthProvider(userID)
	if err != nil {
		// Log but don't fail - default to "email"
		log.Warn().Err(err).Uint("user_id", userID).Msg("Failed to determine auth provider")
		authProvider = "email"
	}

	response := responses.NewCurrentUserResponse(user, authProvider)
	return &response, nil
}

// DeleteAccount deletes a user's account and all associated data.
// For email auth users, password verification is required.
// For social auth users, confirmation string "DELETE" is required.
// Returns a list of file URLs that need to be deleted from disk.
func (s *AuthService) DeleteAccount(userID uint, password, confirmation *string) ([]string, error) {
	// Fetch user
	user, err := s.userRepo.FindByID(userID)
	if err != nil {
		if errors.Is(err, repositories.ErrUserNotFound) {
			return nil, apperrors.NotFound("error.user_not_found")
		}
		return nil, apperrors.Internal(err)
	}

	// Determine auth provider
	authProvider, err := s.userRepo.FindAuthProvider(userID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// Validate credentials based on auth provider
	if authProvider == "email" {
		if password == nil || *password == "" {
			return nil, apperrors.BadRequest("error.password_required")
		}
		if !user.CheckPassword(*password) {
			return nil, apperrors.Unauthorized("error.invalid_credentials")
		}
	} else {
		// Social auth (apple or google) - require confirmation
		if confirmation == nil || *confirmation != "DELETE" {
			return nil, apperrors.BadRequest("error.confirmation_required")
		}
	}

	// Start transaction and cascade delete
	var fileURLs []string
	txErr := s.userRepo.Transaction(func(txRepo *repositories.UserRepository) error {
		urls, err := txRepo.DeleteUserCascade(userID)
		if err != nil {
			return err
		}
		fileURLs = urls
		return nil
	})
	if txErr != nil {
		return nil, apperrors.Internal(txErr)
	}

	return fileURLs, nil
}

// UpdateProfile updates a user's profile
func (s *AuthService) UpdateProfile(userID uint, req *requests.UpdateProfileRequest) (*responses.CurrentUserResponse, error) {
	user, err := s.userRepo.FindByID(userID)
	if err != nil {
		return nil, err
	}

	// Check if new email is taken (if email is being changed)
	if req.Email != nil && *req.Email != user.Email {
		exists, err := s.userRepo.ExistsByEmail(*req.Email)
		if err != nil {
			return nil, apperrors.Internal(err)
		}
		if exists {
			return nil, apperrors.Conflict("error.email_already_taken")
		}
		user.Email = *req.Email
	}

	if req.FirstName != nil {
		user.FirstName = *req.FirstName
	}
	if req.LastName != nil {
		user.LastName = *req.LastName
	}

	if err := s.userRepo.Update(user); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Reload with profile
	user, err = s.userRepo.FindByIDWithProfile(userID)
	if err != nil {
		return nil, err
	}

	authProvider, err := s.userRepo.FindAuthProvider(userID)
	if err != nil {
		log.Warn().Err(err).Uint("user_id", userID).Msg("Failed to determine auth provider")
		authProvider = "email"
	}

	response := responses.NewCurrentUserResponse(user, authProvider)
	return &response, nil
}

// VerifyEmail verifies a user's email with a confirmation code
func (s *AuthService) VerifyEmail(userID uint, code string) error {
	// Get user profile
	profile, err := s.userRepo.GetOrCreateProfile(userID)
	if err != nil {
		return apperrors.Internal(err)
	}

	// Check if already verified
	if profile.Verified {
		return apperrors.BadRequest("error.email_already_verified")
	}

	// Check for test code when DEBUG_FIXED_CODES is enabled
	if s.cfg.Server.DebugFixedCodes && code == "123456" {
		if err := s.userRepo.SetProfileVerified(userID, true); err != nil {
			return apperrors.Internal(err)
		}
		return nil
	}

	// Find and validate confirmation code
	confirmCode, err := s.userRepo.FindConfirmationCode(userID, code)
	if err != nil {
		if errors.Is(err, repositories.ErrCodeNotFound) {
			return apperrors.BadRequest("error.invalid_verification_code")
		}
		if errors.Is(err, repositories.ErrCodeExpired) {
			return apperrors.BadRequest("error.verification_code_expired")
		}
		return apperrors.Internal(err)
	}

	// Mark code as used
	if err := s.userRepo.MarkConfirmationCodeUsed(confirmCode.ID); err != nil {
		return apperrors.Internal(err)
	}

	// Set profile as verified
	if err := s.userRepo.SetProfileVerified(userID, true); err != nil {
		return apperrors.Internal(err)
	}

	return nil
}

// ResendVerificationCode creates and returns a new verification code
func (s *AuthService) ResendVerificationCode(userID uint) (string, error) {
	// Get user profile
	profile, err := s.userRepo.GetOrCreateProfile(userID)
	if err != nil {
		return "", apperrors.Internal(err)
	}

	// Check if already verified
	if profile.Verified {
		return "", apperrors.BadRequest("error.email_already_verified")
	}

	// Generate new code - use fixed code when DEBUG_FIXED_CODES is enabled for easier local testing
	var code string
	if s.cfg.Server.DebugFixedCodes {
		code = "123456"
	} else {
		code = generateSixDigitCode()
	}
	expiresAt := time.Now().UTC().Add(s.cfg.Security.ConfirmationExpiry)

	if _, err := s.userRepo.CreateConfirmationCode(userID, code, expiresAt); err != nil {
		return "", apperrors.Internal(err)
	}

	return code, nil
}

// ForgotPassword initiates the password reset process
func (s *AuthService) ForgotPassword(email string) (string, *models.User, error) {
	// Find user by email
	user, err := s.userRepo.FindByEmail(email)
	if err != nil {
		if errors.Is(err, repositories.ErrUserNotFound) {
			// Don't reveal that the email doesn't exist
			return "", nil, nil
		}
		return "", nil, err
	}

	// Check rate limit
	count, err := s.userRepo.CountRecentPasswordResetRequests(user.ID)
	if err != nil {
		return "", nil, apperrors.Internal(err)
	}
	if count >= int64(s.cfg.Security.MaxPasswordResetRate) {
		return "", nil, apperrors.TooManyRequests("error.rate_limit_exceeded")
	}

	// Generate code and reset token - use fixed code when DEBUG_FIXED_CODES is enabled for easier local testing
	var code string
	if s.cfg.Server.DebugFixedCodes {
		code = "123456"
	} else {
		code = generateSixDigitCode()
	}
	resetToken := generateResetToken()
	expiresAt := time.Now().UTC().Add(s.cfg.Security.PasswordResetExpiry)

	// Hash the code before storing
	codeHash, err := bcrypt.GenerateFromPassword([]byte(code), bcrypt.DefaultCost)
	if err != nil {
		return "", nil, apperrors.Internal(err)
	}

	if _, err := s.userRepo.CreatePasswordResetCode(user.ID, string(codeHash), resetToken, expiresAt); err != nil {
		return "", nil, apperrors.Internal(err)
	}

	return code, user, nil
}

// VerifyResetCode verifies a password reset code and returns a reset token
func (s *AuthService) VerifyResetCode(email, code string) (string, error) {
	// Find the reset code
	resetCode, user, err := s.userRepo.FindPasswordResetCodeByEmail(email)
	if err != nil {
		if errors.Is(err, repositories.ErrUserNotFound) || errors.Is(err, repositories.ErrCodeNotFound) {
			return "", apperrors.BadRequest("error.invalid_verification_code")
		}
		return "", apperrors.Internal(err)
	}

	// Check for test code when DEBUG_FIXED_CODES is enabled
	if s.cfg.Server.DebugFixedCodes && code == "123456" {
		return resetCode.ResetToken, nil
	}

	// Verify the code
	if !resetCode.CheckCode(code) {
		// Increment attempts
		s.userRepo.IncrementResetCodeAttempts(resetCode.ID)
		return "", apperrors.BadRequest("error.invalid_verification_code")
	}

	// Check if code is still valid
	if !resetCode.IsValid() {
		if resetCode.Used {
			return "", apperrors.BadRequest("error.invalid_verification_code")
		}
		if resetCode.Attempts >= resetCode.MaxAttempts {
			return "", apperrors.TooManyRequests("error.rate_limit_exceeded")
		}
		return "", apperrors.BadRequest("error.verification_code_expired")
	}

	_ = user // user available if needed

	return resetCode.ResetToken, nil
}

// ResetPassword resets the user's password using a reset token
func (s *AuthService) ResetPassword(resetToken, newPassword string) error {
	// Find the reset code by token
	resetCode, err := s.userRepo.FindPasswordResetCodeByToken(resetToken)
	if err != nil {
		if errors.Is(err, repositories.ErrCodeNotFound) || errors.Is(err, repositories.ErrCodeExpired) {
			return apperrors.BadRequest("error.invalid_reset_token")
		}
		return apperrors.Internal(err)
	}

	// Get the user
	user, err := s.userRepo.FindByID(resetCode.UserID)
	if err != nil {
		return apperrors.Internal(err)
	}

	// Update password
	if err := user.SetPassword(newPassword); err != nil {
		return apperrors.Internal(err)
	}

	if err := s.userRepo.Update(user); err != nil {
		return apperrors.Internal(err)
	}

	// Mark reset code as used
	if err := s.userRepo.MarkPasswordResetCodeUsed(resetCode.ID); err != nil {
		// Log error but don't fail
		log.Warn().Err(err).Uint("reset_code_id", resetCode.ID).Msg("Failed to mark reset code as used")
	}

	// Invalidate all existing tokens for this user (security measure)
	if err := s.userRepo.DeleteTokenByUserID(user.ID); err != nil {
		// Log error but don't fail
		log.Warn().Err(err).Uint("user_id", user.ID).Msg("Failed to delete user tokens after password reset")
	}

	return nil
}

// AppleSignIn handles Sign in with Apple authentication
func (s *AuthService) AppleSignIn(ctx context.Context, appleAuth *AppleAuthService, req *requests.AppleSignInRequest) (*responses.AppleSignInResponse, error) {
	// 1. Verify the Apple JWT token
	claims, err := appleAuth.VerifyIdentityToken(ctx, req.IDToken)
	if err != nil {
		return nil, apperrors.Unauthorized("error.invalid_credentials").Wrap(err)
	}

	// Use the subject from claims as the authoritative Apple ID
	appleID := claims.Subject
	if appleID == "" {
		appleID = req.UserID // Fallback to request UserID
	}

	// 2. Check if this Apple ID is already linked to an account
	existingAuth, err := s.userRepo.FindByAppleID(appleID)
	if err == nil && existingAuth != nil {
		// User already linked with this Apple ID - log them in
		user, err := s.userRepo.FindByIDWithProfile(existingAuth.UserID)
		if err != nil {
			return nil, apperrors.Internal(err)
		}

		if !user.IsActive {
			return nil, apperrors.Unauthorized("error.account_inactive")
		}

		// Get or create token
		token, err := s.userRepo.GetOrCreateToken(user.ID)
		if err != nil {
			return nil, apperrors.Internal(err)
		}

		// Update last login
		_ = s.userRepo.UpdateLastLogin(user.ID)

		return &responses.AppleSignInResponse{
			Token:     token.Key,
			User:      responses.NewUserResponse(user),
			IsNewUser: false,
		}, nil
	}

	// 3. Check if email matches an existing user (for account linking)
	email := getEmailFromRequest(req.Email, claims.Email)
	if email != "" {
		existingUser, err := s.userRepo.FindByEmail(email)
		if err == nil && existingUser != nil {
			// S-06: Log auto-linking of social account to existing user
			log.Warn().
				Str("email", email).
				Str("provider", "apple").
				Uint("user_id", existingUser.ID).
				Msg("Auto-linking social account to existing user by email match")

			// Link Apple ID to existing account
			appleAuthRecord := &models.AppleSocialAuth{
				UserID:         existingUser.ID,
				AppleID:        appleID,
				Email:          email,
				IsPrivateEmail: isPrivateRelayEmail(email) || claims.IsPrivateRelayEmail(),
			}
			if err := s.userRepo.CreateAppleSocialAuth(appleAuthRecord); err != nil {
				return nil, apperrors.Internal(err)
			}

			// Mark as verified since Apple verified the email
			_ = s.userRepo.SetProfileVerified(existingUser.ID, true)

			// Get or create token
			token, err := s.userRepo.GetOrCreateToken(existingUser.ID)
			if err != nil {
				return nil, apperrors.Internal(err)
			}

			// Update last login
			_ = s.userRepo.UpdateLastLogin(existingUser.ID)

			// B-08: Check error from FindByIDWithProfile
			existingUser, err = s.userRepo.FindByIDWithProfile(existingUser.ID)
			if err != nil {
				return nil, apperrors.Internal(err)
			}

			return &responses.AppleSignInResponse{
				Token:     token.Key,
				User:      responses.NewUserResponse(existingUser),
				IsNewUser: false,
			}, nil
		}
	}

	// 4. Create new user
	username := generateUniqueUsername(email, req.FirstName)

	user := &models.User{
		Username:  username,
		Email:     getEmailOrDefault(email),
		FirstName: getStringOrEmpty(req.FirstName),
		LastName:  getStringOrEmpty(req.LastName),
		IsActive:  true,
	}

	// Set a random password (user won't use it since they log in with Apple)
	randomPassword := generateResetToken()
	_ = user.SetPassword(randomPassword)

	if err := s.userRepo.Create(user); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Create profile (already verified since Apple verified)
	profile, _ := s.userRepo.GetOrCreateProfile(user.ID)
	if profile != nil {
		_ = s.userRepo.SetProfileVerified(user.ID, true)
	}

	// Create notification preferences with all options enabled
	if s.notificationRepo != nil {
		if _, err := s.notificationRepo.GetOrCreatePreferences(user.ID); err != nil {
			log.Warn().Err(err).Uint("user_id", user.ID).Msg("Failed to create notification preferences for Apple Sign In user")
		}
	}

	// Link Apple ID
	appleAuthRecord := &models.AppleSocialAuth{
		UserID:         user.ID,
		AppleID:        appleID,
		Email:          getEmailOrDefault(email),
		IsPrivateEmail: isPrivateRelayEmail(email) || claims.IsPrivateRelayEmail(),
	}
	if err := s.userRepo.CreateAppleSocialAuth(appleAuthRecord); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Create token
	token, err := s.userRepo.GetOrCreateToken(user.ID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// B-08: Check error from FindByIDWithProfile
	user, err = s.userRepo.FindByIDWithProfile(user.ID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	return &responses.AppleSignInResponse{
		Token:     token.Key,
		User:      responses.NewUserResponse(user),
		IsNewUser: true,
	}, nil
}

// GoogleSignIn handles Google Sign In authentication
func (s *AuthService) GoogleSignIn(ctx context.Context, googleAuth *GoogleAuthService, req *requests.GoogleSignInRequest) (*responses.GoogleSignInResponse, error) {
	// 1. Verify the Google ID token
	tokenInfo, err := googleAuth.VerifyIDToken(ctx, req.IDToken)
	if err != nil {
		return nil, apperrors.Unauthorized("error.invalid_credentials").Wrap(err)
	}

	googleID := tokenInfo.Sub
	if googleID == "" {
		return nil, apperrors.Unauthorized("error.invalid_credentials")
	}

	// 2. Check if this Google ID is already linked to an account
	existingAuth, err := s.userRepo.FindByGoogleID(googleID)
	if err == nil && existingAuth != nil {
		// User already linked with this Google ID - log them in
		user, err := s.userRepo.FindByIDWithProfile(existingAuth.UserID)
		if err != nil {
			return nil, apperrors.Internal(err)
		}

		if !user.IsActive {
			return nil, apperrors.Unauthorized("error.account_inactive")
		}

		// Get or create token
		token, err := s.userRepo.GetOrCreateToken(user.ID)
		if err != nil {
			return nil, apperrors.Internal(err)
		}

		// Update last login
		_ = s.userRepo.UpdateLastLogin(user.ID)

		return &responses.GoogleSignInResponse{
			Token:     token.Key,
			User:      responses.NewUserResponse(user),
			IsNewUser: false,
		}, nil
	}

	// 3. Check if email matches an existing user (for account linking)
	email := tokenInfo.Email
	if email != "" {
		existingUser, err := s.userRepo.FindByEmail(email)
		if err == nil && existingUser != nil {
			// S-06: Log auto-linking of social account to existing user
			log.Warn().
				Str("email", email).
				Str("provider", "google").
				Uint("user_id", existingUser.ID).
				Msg("Auto-linking social account to existing user by email match")

			// Link Google ID to existing account
			googleAuthRecord := &models.GoogleSocialAuth{
				UserID:   existingUser.ID,
				GoogleID: googleID,
				Email:    email,
				Name:     tokenInfo.Name,
				Picture:  tokenInfo.Picture,
			}
			if err := s.userRepo.CreateGoogleSocialAuth(googleAuthRecord); err != nil {
				return nil, apperrors.Internal(err)
			}

			// Mark as verified since Google verified the email
			if tokenInfo.IsEmailVerified() {
				_ = s.userRepo.SetProfileVerified(existingUser.ID, true)
			}

			// Get or create token
			token, err := s.userRepo.GetOrCreateToken(existingUser.ID)
			if err != nil {
				return nil, apperrors.Internal(err)
			}

			// Update last login
			_ = s.userRepo.UpdateLastLogin(existingUser.ID)

			// B-08: Check error from FindByIDWithProfile
			existingUser, err = s.userRepo.FindByIDWithProfile(existingUser.ID)
			if err != nil {
				return nil, apperrors.Internal(err)
			}

			return &responses.GoogleSignInResponse{
				Token:     token.Key,
				User:      responses.NewUserResponse(existingUser),
				IsNewUser: false,
			}, nil
		}
	}

	// 4. Create new user
	username := generateGoogleUsername(email, tokenInfo.GivenName)

	user := &models.User{
		Username:  username,
		Email:     email,
		FirstName: tokenInfo.GivenName,
		LastName:  tokenInfo.FamilyName,
		IsActive:  true,
	}

	// Set a random password (user won't use it since they log in with Google)
	randomPassword := generateResetToken()
	_ = user.SetPassword(randomPassword)

	if err := s.userRepo.Create(user); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Create profile (already verified if Google verified email)
	profile, _ := s.userRepo.GetOrCreateProfile(user.ID)
	if profile != nil && tokenInfo.IsEmailVerified() {
		_ = s.userRepo.SetProfileVerified(user.ID, true)
	}

	// Create notification preferences with all options enabled
	if s.notificationRepo != nil {
		if _, err := s.notificationRepo.GetOrCreatePreferences(user.ID); err != nil {
			log.Warn().Err(err).Uint("user_id", user.ID).Msg("Failed to create notification preferences for Google Sign In user")
		}
	}

	// Link Google ID
	googleAuthRecord := &models.GoogleSocialAuth{
		UserID:   user.ID,
		GoogleID: googleID,
		Email:    email,
		Name:     tokenInfo.Name,
		Picture:  tokenInfo.Picture,
	}
	if err := s.userRepo.CreateGoogleSocialAuth(googleAuthRecord); err != nil {
		return nil, apperrors.Internal(err)
	}

	// Create token
	token, err := s.userRepo.GetOrCreateToken(user.ID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	// B-08: Check error from FindByIDWithProfile
	user, err = s.userRepo.FindByIDWithProfile(user.ID)
	if err != nil {
		return nil, apperrors.Internal(err)
	}

	return &responses.GoogleSignInResponse{
		Token:     token.Key,
		User:      responses.NewUserResponse(user),
		IsNewUser: true,
	}, nil
}

// Helper functions

func generateSixDigitCode() string {
	b := make([]byte, 4)
	rand.Read(b)
	num := int(b[0])<<24 | int(b[1])<<16 | int(b[2])<<8 | int(b[3])
	if num < 0 {
		num = -num
	}
	code := num % 1000000
	return fmt.Sprintf("%06d", code)
}

func generateResetToken() string {
	b := make([]byte, 32)
	rand.Read(b)
	return hex.EncodeToString(b)
}

// Helper functions for Apple Sign In

func getEmailFromRequest(reqEmail *string, claimsEmail string) string {
	if reqEmail != nil && *reqEmail != "" {
		return *reqEmail
	}
	return claimsEmail
}

func getEmailOrDefault(email string) string {
	if email == "" {
		// Generate a placeholder email for users without one
		return fmt.Sprintf("apple_%s@privaterelay.appleid.com", generateResetToken()[:16])
	}
	return email
}

func getStringOrEmpty(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

func isPrivateRelayEmail(email string) bool {
	return strings.HasSuffix(strings.ToLower(email), "@privaterelay.appleid.com")
}

func generateUniqueUsername(email string, firstName *string) string {
	// Try using first part of email
	if email != "" && !isPrivateRelayEmail(email) {
		parts := strings.Split(email, "@")
		if len(parts) > 0 && parts[0] != "" {
			// Add random suffix to ensure uniqueness
			return parts[0] + "_" + generateResetToken()[:6]
		}
	}

	// Try using first name
	if firstName != nil && *firstName != "" {
		return strings.ToLower(*firstName) + "_" + generateResetToken()[:6]
	}

	// Fallback to random username
	return "user_" + generateResetToken()[:10]
}

func generateGoogleUsername(email string, firstName string) string {
	// Try using first part of email
	if email != "" {
		parts := strings.Split(email, "@")
		if len(parts) > 0 && parts[0] != "" {
			// Add random suffix to ensure uniqueness
			return parts[0] + "_" + generateResetToken()[:6]
		}
	}

	// Try using first name
	if firstName != "" {
		return strings.ToLower(firstName) + "_" + generateResetToken()[:6]
	}

	// Fallback to random username
	return "google_" + generateResetToken()[:10]
}
