// service/email_service.go
package service

import (
	"fmt"
	"time"

	"api_bb/internal/models"
	"api_bb/internal/repository"
	"api_bb/pkg/email"
	"api_bb/pkg/logger"

	"github.com/google/uuid"
	"go.uber.org/zap"
)

type EmailService struct {
	emailRepo      repository.EmailRepository
	userRepo       repository.UserRepository
	emailSender    email.Service
	logger         *zap.Logger
	tokenExpiry    time.Duration
	passwordExpiry time.Duration
}

func NewEmailService(
	emailRepo repository.EmailRepository,
	userRepo repository.UserRepository,
	emailSender email.Service,
) EmailService {
	// Создаем логгер с контекстом для сервиса
	serviceLogger := logger.Get().With(zap.String("service", "email"))

	return EmailService{
		emailRepo:      emailRepo,
		userRepo:       userRepo,
		emailSender:    emailSender,
		logger:         serviceLogger,
		tokenExpiry:    24 * time.Hour, // 24 часа для верификации
		passwordExpiry: 1 * time.Hour,  // 1 час для сброса пароля
	}
}

func (s *EmailService) SendVerificationEmail(userID uint, email, userName string) error {
	s.logger.Info("Sending verification email",
		zap.Uint("user_id", userID),
		zap.String("email", email),
	)

	token := uuid.New().String()

	verification := &models.EmailVerification{
		UserID:    userID,
		Token:     token,
		Email:     email,
		Type:      "verification",
		ExpiresAt: time.Now().Add(s.tokenExpiry),
	}

	if err := s.emailRepo.CreateVerificationToken(verification); err != nil {
		s.logger.Error("Failed to create verification token",
			zap.Uint("user_id", userID),
			zap.String("email", email),
			zap.Error(err),
		)
		return fmt.Errorf("failed to create verification token: %w", err)
	}

	if err := s.emailSender.SendVerificationEmail(email, userName, token); err != nil {
		s.logger.Error("Failed to send verification email",
			zap.Uint("user_id", userID),
			zap.String("email", email),
			zap.Error(err),
		)
		return fmt.Errorf("failed to send verification email: %w", err)
	}

	s.logger.Info("Verification email sent successfully",
		zap.Uint("user_id", userID),
		zap.String("email", email))
	return nil
}

func (s *EmailService) VerifyEmail(token string) error {
	s.logger.Info("Verifying email token",
		zap.String("token", token),
	)

	verification, err := s.emailRepo.GetVerificationToken(token)
	if err != nil {
		s.logger.Error("Invalid or expired verification token",
			zap.String("token", token),
			zap.Error(err),
		)
		return fmt.Errorf("invalid or expired token: %w", err)
	}

	if verification.Type != "verification" {
		s.logger.Error("Invalid token type for email verification",
			zap.String("token", token),
			zap.String("type", verification.Type),
		)
		return fmt.Errorf("invalid token type")
	}

	// Обновляем пользователя
	if err := s.userRepo.MarkEmailAsVerified(verification.UserID); err != nil {
		s.logger.Error("Failed to verify email in user repository",
			zap.Uint("user_id", verification.UserID),
			zap.String("email", verification.Email),
			zap.Error(err),
		)
		return fmt.Errorf("failed to verify email: %w", err)
	}

	// Помечаем токен как использованный
	if err := s.emailRepo.MarkTokenAsUsed(token); err != nil {
		s.logger.Error("Failed to mark token as used",
			zap.Error(err),
			zap.String("token", token))
	}

	s.logger.Info("Email verified successfully",
		zap.Uint("user_id", verification.UserID),
		zap.String("email", verification.Email))
	return nil
}

func (s *EmailService) SendPasswordResetEmail(email string) error {
	s.logger.Info("Sending password reset email",
		zap.String("email", email),
	)

	user, err := s.userRepo.FindByEmail(email)
	if err != nil {
		// Для безопасности не сообщаем, существует ли email
		s.logger.Info("Password reset requested for non-existent email",
			zap.String("email", email))
		return nil
	}

	token := uuid.New().String()

	resetRequest := &models.EmailVerification{
		UserID:    user.ID,
		Token:     token,
		Email:     email,
		Type:      "password_reset",
		ExpiresAt: time.Now().Add(s.passwordExpiry),
	}

	if err := s.emailRepo.CreateVerificationToken(resetRequest); err != nil {
		s.logger.Error("Failed to create password reset token",
			zap.Uint("user_id", user.ID),
			zap.String("email", email),
			zap.Error(err),
		)
		return fmt.Errorf("failed to create password reset token: %w", err)
	}

	if err := s.emailSender.SendPasswordResetEmail(email, user.FirstName, token); err != nil {
		s.logger.Error("Failed to send password reset email",
			zap.Uint("user_id", user.ID),
			zap.String("email", email),
			zap.Error(err),
		)
		return fmt.Errorf("failed to send password reset email: %w", err)
	}

	s.logger.Info("Password reset email sent successfully",
		zap.Uint("user_id", user.ID),
		zap.String("email", email))
	return nil
}

func (s *EmailService) ResetPassword(token, newPassword string) error {
	s.logger.Info("Resetting password with token",
		zap.String("token", token),
	)

	verification, err := s.emailRepo.GetVerificationToken(token)
	if err != nil {
		s.logger.Error("Invalid or expired password reset token",
			zap.String("token", token),
			zap.Error(err),
		)
		return fmt.Errorf("invalid or expired token: %w", err)
	}

	if verification.Type != "password_reset" {
		s.logger.Error("Invalid token type for password reset",
			zap.String("token", token),
			zap.String("type", verification.Type),
		)
		return fmt.Errorf("invalid token type")
	}

	// Обновляем пароль пользователя
	if err := s.userRepo.UpdatePassword(verification.UserID, newPassword); err != nil {
		s.logger.Error("Failed to update password",
			zap.Uint("user_id", verification.UserID),
			zap.Error(err),
		)
		return fmt.Errorf("failed to update password: %w", err)
	}

	// Помечаем токен как использованный
	if err := s.emailRepo.MarkTokenAsUsed(token); err != nil {
		s.logger.Error("Failed to mark token as used",
			zap.Error(err),
			zap.String("token", token))
	}

	s.logger.Info("Password reset successfully",
		zap.Uint("user_id", verification.UserID))
	return nil
}

func (s *EmailService) SendNewsletterToSubscribers(subject, content string) error {
	s.logger.Info("Sending newsletter to subscribers",
		zap.String("subject", subject),
	)

	subscribers, err := s.emailRepo.GetUsersWithNewsletter()
	if err != nil {
		s.logger.Error("Failed to get subscribers",
			zap.Error(err),
		)
		return fmt.Errorf("failed to get subscribers: %w", err)
	}

	s.logger.Debug("Found subscribers for newsletter",
		zap.Int("count", len(subscribers)),
	)

	var errors []error
	for _, user := range subscribers {
		if err := s.emailSender.SendNewsletterEmail(user.Email, user.FirstName, subject, content); err != nil {
			s.logger.Error("Failed to send newsletter to user",
				zap.Uint("user_id", user.ID),
				zap.String("email", user.Email),
				zap.Error(err))
			errors = append(errors, err)
			continue
		}
		s.logger.Debug("Newsletter sent to user",
			zap.Uint("user_id", user.ID),
			zap.String("email", user.Email))
	}

	if len(errors) > 0 {
		s.logger.Error("Failed to send newsletter to some users",
			zap.Int("failed_count", len(errors)),
			zap.Int("total_subscribers", len(subscribers)),
		)
		return fmt.Errorf("failed to send newsletter to %d users", len(errors))
	}

	s.logger.Info("Newsletter sent to all subscribers",
		zap.Int("total_subscribers", len(subscribers)))
	return nil
}

func (s *EmailService) CleanupExpiredTokens() error {
	s.logger.Info("Cleaning up expired tokens")

	if err := s.emailRepo.DeleteExpiredTokens(); err != nil {
		s.logger.Error("Failed to cleanup expired tokens",
			zap.Error(err),
		)
		return fmt.Errorf("failed to cleanup expired tokens: %w", err)
	}
	s.logger.Info("Expired tokens cleaned up successfully")
	return nil
}

// GetUserByID возвращает пользователя по ID
func (s *EmailService) GetUserByID(userID uint) (*models.User, error) {
	s.logger.Info("Getting user by ID",
		zap.Uint("user_id", userID),
	)
	
	user, err := s.userRepo.GetUserByID(userID)
	if err != nil {
		s.logger.Error("Failed to get user by ID",
			zap.Uint("user_id", userID),
			zap.Error(err),
		)
		return nil, fmt.Errorf("failed to get user: %w", err)
	}

	s.logger.Debug("User retrieved successfully",
		zap.Uint("user_id", userID),
		zap.String("email", user.Email),
	)
	return user, nil
}
