package services

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"log"
	"time"

	"backend/internal/apperrors"
	"backend/internal/repository"

	"golang.org/x/crypto/bcrypt"
)

type PasswordResetService struct {
	clientUserRepo      *repository.ClientUserRepository
	passwordResetRepo   *repository.PasswordResetRepository
	otpService          *OTPService
	emailQueue          EmailQueue
}

// EmailQueue interface for enqueueing email jobs
type EmailQueue interface {
	EnqueueSendOTPEmail(email, otp string) error
}

func NewPasswordResetService(
	clientUserRepo *repository.ClientUserRepository,
	passwordResetRepo *repository.PasswordResetRepository,
	otpService *OTPService,
	emailQueue EmailQueue,
) *PasswordResetService {
	return &PasswordResetService{
		clientUserRepo:    clientUserRepo,
		passwordResetRepo: passwordResetRepo,
		otpService:        otpService,
		emailQueue:        emailQueue,
	}
}

// RequestPasswordReset sends OTP to user's email
func (s *PasswordResetService) RequestPasswordReset(ctx context.Context, email string) error {
	log.Printf("RequestPasswordReset: Starting password reset request for email: %s", email)
	
	// Find user by email
	user, err := s.clientUserRepo.FindByEmail(ctx, email)
	if err != nil {
		// Check if it's a "not found" error
		if err.Error() == "client user not found" {
			log.Printf("RequestPasswordReset: User not found for email: %s", email)
			return apperrors.ErrAccountNotFound
		}
		// For other database errors, log and return wrapped error
		log.Printf("RequestPasswordReset: Database error finding user by email %s: %v", email, err)
		return fmt.Errorf("failed to find user by email: %w", err)
	}

	userEmailStr := "nil"
	if user.Email != nil {
		userEmailStr = *user.Email
	}
	log.Printf("RequestPasswordReset: User found - ID: %s, Email: %s, Active: %v", user.ID, userEmailStr, user.IsActive)

	// Check if user is active
	if !user.IsActive {
		log.Printf("RequestPasswordReset: User account is inactive for email: %s", email)
		return apperrors.ErrAccountInactive
	}

	// Generate OTP
	otp, err := s.otpService.GenerateOTP()
	if err != nil {
		log.Printf("RequestPasswordReset: Failed to generate OTP for email %s: %v", email, err)
		return fmt.Errorf("failed to generate OTP: %w", err)
	}
	log.Printf("RequestPasswordReset: OTP generated for email: %s", email)

	// Invalidate previous tokens
	if err := s.passwordResetRepo.InvalidatePreviousTokens(ctx, user.ID); err != nil {
		// Log but don't fail - this is not critical
		log.Printf("RequestPasswordReset: Warning - failed to invalidate previous tokens for user %s: %v", user.ID, err)
	}

	// Create OTP record
	expiresAt := s.otpService.GetOTPExpiry()
	userEmail := email
	if user.Email != nil {
		userEmail = *user.Email
	}

	err = s.passwordResetRepo.CreateOTP(ctx, user.ID, userEmail, otp, expiresAt)
	if err != nil {
		log.Printf("RequestPasswordReset: Failed to create OTP record for email %s: %v", email, err)
		return fmt.Errorf("failed to create OTP record: %w", err)
	}
	log.Printf("RequestPasswordReset: OTP record created for email: %s", email)

	// Enqueue OTP email job
	err = s.emailQueue.EnqueueSendOTPEmail(userEmail, otp)
	if err != nil {
		log.Printf("RequestPasswordReset: Failed to enqueue OTP email job for %s: %v", userEmail, err)
		// Log queue error but don't fail the request if OTP was created
		// The user can request a new OTP if queue fails
		return fmt.Errorf("failed to enqueue email job: %w", err)
	}
	log.Printf("RequestPasswordReset: OTP email job enqueued successfully for: %s", userEmail)

	return nil
}

// VerifyOTP verifies the OTP and generates a reset token
func (s *PasswordResetService) VerifyOTP(ctx context.Context, email, otp string) (string, error) {
	// Find token by email and OTP
	token, err := s.passwordResetRepo.FindByEmailAndOTP(ctx, email, otp)
	if err != nil {
		return "", apperrors.NewAuthError("invalid_otp", "Invalid or expired OTP code", "INVALID_OTP")
	}

	// Check if OTP is expired
	if s.otpService.IsOTPExpired(token.OTPExpiresAt) {
		return "", apperrors.NewAuthError("expired_otp", "OTP code has expired", "EXPIRED_OTP")
	}

	// Generate reset token
	resetToken, err := s.generateResetToken()
	if err != nil {
		return "", fmt.Errorf("failed to generate reset token: %w", err)
	}

	// Update token with reset token (expires in 1 hour)
	resetTokenExpiresAt := time.Now().Add(1 * time.Hour)
	err = s.passwordResetRepo.UpdateWithResetToken(ctx, token.ID, resetToken, resetTokenExpiresAt)
	if err != nil {
		return "", fmt.Errorf("failed to update token: %w", err)
	}

	return resetToken, nil
}

// ResetPassword resets the password using the reset token
func (s *PasswordResetService) ResetPassword(ctx context.Context, resetToken, newPassword string) error {
	// Find token by reset token
	token, err := s.passwordResetRepo.FindByResetToken(ctx, resetToken)
	if err != nil {
		return apperrors.NewAuthError("invalid_token", "Invalid or expired reset token", "INVALID_RESET_TOKEN")
	}

	// Check if reset token is expired
	if token.ResetTokenExpiresAt == nil || time.Now().After(*token.ResetTokenExpiresAt) {
		return apperrors.NewAuthError("expired_token", "Reset token has expired", "EXPIRED_RESET_TOKEN")
	}

	// Hash new password
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
	if err != nil {
		return fmt.Errorf("failed to hash password: %w", err)
	}

	// Update user password
	err = s.clientUserRepo.UpdatePassword(ctx, token.ClientUserID, string(hashedPassword))
	if err != nil {
		return fmt.Errorf("failed to update password: %w", err)
	}

	// Mark token as used
	err = s.passwordResetRepo.MarkAsUsed(ctx, token.ID)
	if err != nil {
		return fmt.Errorf("failed to mark token as used: %w", err)
	}

	return nil
}

// generateResetToken generates a secure random token
func (s *PasswordResetService) generateResetToken() (string, error) {
	bytes := make([]byte, 32) // 256 bits
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}

