UserService.java

package com.ctrlbuy.webshop.service;

// ===== DTO IMPORTS =====
import com.ctrlbuy.webshop.dto.RegisterRequest;
import com.ctrlbuy.webshop.dto.RegistrationResult;

// ===== SECURITY IMPORTS =====
import com.ctrlbuy.webshop.security.entity.User;
import com.ctrlbuy.webshop.security.repository.UserRepository;

// ===== JPA IMPORTS =====
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

// ===== SPRING IMPORTS =====
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

// ===== LOGGING IMPORTS =====
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// ===== JAVA STANDARD LIBRARY IMPORTS =====
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.UUID;

@Service
public class UserService {

    private static final Logger logger = LoggerFactory.getLogger(UserService.class);

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @PersistenceContext
    private EntityManager entityManager;

    // ===== REGISTRATION METHODS =====

    public RegistrationResult registerUser(RegisterRequest request) {
        logger.info("Attempting to register user with email: {}", request.getEmail());

        try {
            if (userRepository.findByEmail(request.getEmail()).isPresent()) {
                logger.warn("Registration failed - email already exists: {}", request.getEmail());
                return new RegistrationResult(false, "Email already exists");
            }

            User user = new User();
            user.setEmail(request.getEmail());
            user.setPassword(passwordEncoder.encode(request.getPassword()));
            user.setFirstName(request.getFirstName());
            user.setLastName(request.getLastName());
            user.setEnabled(true);
            user.setCreatedAt(LocalDateTime.now());

            User savedUser = userRepository.save(user);
            logger.info("User registered successfully with ID: {}", savedUser.getId());

            return new RegistrationResult(true, "User registered successfully");

        } catch (Exception e) {
            logger.error("Error during user registration for email: {}", request.getEmail(), e);
            return new RegistrationResult(false, "Registration failed: " + e.getMessage());
        }
    }

    @Transactional
    public RegistrationResult registerNewUserWithToken(RegisterRequest request) {
        logger.info("Registering new user with token for email: {}", request.getEmail());

        if (userRepository.existsByUsername(request.getUsername())) {
            throw new RuntimeException("Användarnamnet är redan taget");
        }

        if (userRepository.existsByEmail(request.getEmail())) {
            throw new RuntimeException("E-postadressen är redan registrerad");
        }

        User user = new User();
        user.setUsername(request.getUsername());
        user.setEmail(request.getEmail());
        user.setPassword(passwordEncoder.encode(request.getPassword()));
        user.setFirstName(request.getFirstName());
        user.setLastName(request.getLastName());
        user.setActive(true);
        user.setEmailVerified(false);

        String token = UUID.randomUUID().toString();
        user.setVerificationToken(token);
        user.setVerificationTokenExpiry(LocalDateTime.now().plusHours(24));
        user.setCreatedAt(LocalDateTime.now());

        User savedUser = userRepository.save(user);
        logger.info("User registered successfully with ID: {}", savedUser.getId());

        return new RegistrationResult(savedUser, token);
    }

    // ===== USER RETRIEVAL METHODS =====

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public List<User> getActiveUsers() {
        return userRepository.findByActiveTrue();
    }

    public List<User> getInactiveUsers() {
        return userRepository.findByEnabledFalse();
    }

    public Optional<User> findByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    public User findByUsernameUser(String username) {
        return userRepository.findByUsername(username).orElse(null);
    }

    public Optional<User> findByEmail(String email) {
        return userRepository.findByEmail(email);
    }

    public User findByEmailUser(String email) {
        return userRepository.findByEmail(email).orElse(null);
    }

    public User findById(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    public Optional<User> findByIdOptional(Long id) {
        return userRepository.findById(id);
    }

    public User findByResetToken(String token) {
        return userRepository.findByResetToken(token).orElse(null);
    }

    // ===== USER COUNT METHODS =====

    public long countAllUsers() {
        return userRepository.count();
    }

    public long countActiveUsers() {
        return userRepository.countByActiveTrue();
    }

    // ===== USER EXISTENCE CHECKS =====

    public boolean existsByUsername(String username) {
        return userRepository.findByUsernameAndActiveTrue(username).isPresent();
    }

    public boolean existsByEmail(String email) {
        return userRepository.findByEmailAndActiveTrue(email).isPresent();
    }

    public boolean existsByEmailIncludingInactive(String email) {
        return userRepository.existsByEmail(email);
    }

    public boolean existsByUsernameIncludingInactive(String username) {
        return userRepository.existsByUsername(username);
    }

    // ===== USER SAVE METHODS =====

    @Transactional
    public User save(User user) {
        return userRepository.save(user);
    }

    @Transactional
    public void saveUser(User user) {
        userRepository.save(user);
    }

    // ===== USER STATUS MANAGEMENT =====

    @Transactional
    public void toggleUserStatus(Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("User not found"));
        user.setEnabled(!user.isEnabled());
        userRepository.save(user);
        logger.info("User {} status toggled to: {}", userId, user.isEnabled());
    }

    @Transactional
    public void toggleUserActive(Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("User finns inte"));
        user.setActive(!user.isActive());
        userRepository.save(user);
        logger.info("User {} active status toggled to: {}", userId, user.isActive());
    }

    @Transactional
    public boolean deactivateUser(long userId) {
        try {
            User user = userRepository.findById(userId)
                    .orElseThrow(() -> new RuntimeException("User not found"));

            if ("admin".equals(user.getUsername())) {
                return false;
            }

            if (user.getRoles() != null) {
                String roles = user.getRoles().toString().toLowerCase();
                if (roles.contains("admin") || roles.contains("role_admin")) {
                    return false;
                }
            }

            user.setActive(false);
            userRepository.save(user);
            logger.info("User {} deactivated", userId);
            return true;
        } catch (Exception e) {
            logger.error("Error deactivating user {}: {}", userId, e.getMessage());
            return false;
        }
    }

    // ===== USER DELETION =====

    @Transactional
    public void deletePermanently(Long userId) {
        logger.info("🗑️ Startar permanent borttagning av user ID: {}", userId);

        User user = userRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("Användare med ID " + userId + " hittades inte"));

        logger.info("👤 Hittat användare: {} {}", user.getFirstName(), user.getLastName());

        if ("fredrik".equalsIgnoreCase(user.getUsername())) {
            throw new RuntimeException("⛔ Kan inte radera huvudadmin 'fredrik'");
        }

        if (user.isActive()) {
            throw new RuntimeException("⛔ Kan endast radera inaktiva användare. Inaktivera användaren först.");
        }

        try {
            logger.info("🧹 Raderar order_items för användarens orders...");
            int deletedOrderItems = entityManager.createNativeQuery(
                            "DELETE FROM order_items WHERE order_id IN (SELECT id FROM orders WHERE user_id = ?)")
                    .setParameter(1, userId)
                    .executeUpdate();
            logger.info("🧹 Raderade {} order_items", deletedOrderItems);

            logger.info("🧹 Raderar orders...");
            int deletedOrders = entityManager.createNativeQuery(
                            "DELETE FROM orders WHERE user_id = ?")
                    .setParameter(1, userId)
                    .executeUpdate();
            logger.info("🧹 Raderade {} orders", deletedOrders);

            // KOMMENTERAT BORT - REVIEWS-TABELLEN EXISTERAR INTE
            // logger.info("🧹 Raderar reviews...");
            // int deletedReviews = entityManager.createNativeQuery(
            //                 "DELETE FROM reviews WHERE user_id = ?")
            //         .setParameter(1, userId)
            //         .executeUpdate();
            // logger.info("🧹 Raderade {} reviews", deletedReviews);

            logger.info("🧹 Raderar user_roles...");
            int deletedRoles = entityManager.createNativeQuery(
                            "DELETE FROM user_roles WHERE user_id = ?")
                    .setParameter(1, userId)
                    .executeUpdate();
            logger.info("🧹 Raderade {} user_roles", deletedRoles);

            logger.info("🧹 Raderar användaren...");
            userRepository.deleteById(userId);

            logger.info("✅ Användare {} permanent borttagen!", userId);

        } catch (Exception e) {
            logger.error("❌ Fel vid permanent borttagning av användare {}: {}", userId, e.getMessage());
            throw new RuntimeException("Kunde inte radera användaren: " + e.getMessage(), e);
        }
    }

    // ===== EMAIL VERIFICATION =====

    @Transactional
    public boolean verifyEmail(String token) {
        logger.info("Attempting to verify email with token: {}", token);

        Optional<User> userOpt = userRepository.findByVerificationToken(token);
        if (userOpt.isEmpty()) {
            logger.warn("Invalid verification token: {}", token);
            return false;
        }

        User user = userOpt.get();
        if (!user.isVerificationTokenValid()) {
            logger.warn("Verification token expired for user: {}", user.getEmail());
            return false;
        }

        user.setEmailVerified(true);
        user.setVerificationToken(null);
        user.setVerificationTokenExpiry(null);
        userRepository.save(user);

        logger.info("Email verified successfully for user: {}", user.getEmail());
        return true;
    }

    @Transactional
    public String createNewVerificationToken(String email) {
        Optional<User> userOpt = userRepository.findByEmail(email);
        if (userOpt.isEmpty()) {
            return null;
        }

        User user = userOpt.get();
        String newToken = UUID.randomUUID().toString();
        user.setVerificationToken(newToken);
        user.setVerificationTokenExpiry(LocalDateTime.now().plusHours(24));
        userRepository.save(user);

        logger.info("New verification token created for user: {}", email);
        return newToken;
    }

    @Transactional
    public void resetUserVerification(Long userId) {
        User user = userRepository.findById(userId)
                .orElseThrow(() -> new RuntimeException("User not found"));
        user.setEmailVerified(false);
        user.setVerificationToken(UUID.randomUUID().toString());
        user.setVerificationTokenExpiry(LocalDateTime.now().plusHours(24));
        userRepository.save(user);
        logger.info("User {} verification reset", userId);
    }

    // ===== PASSWORD RESET METHODS =====

    @Transactional
    public boolean initiatePasswordReset(String email) {
        try {
            Optional<User> userOpt = userRepository.findByEmailAndActiveTrue(email);
            if (userOpt.isEmpty()) {
                return false;
            }

            User user = userOpt.get();
            String resetToken = UUID.randomUUID().toString();
            user.setResetToken(resetToken);
            user.setResetTokenExpiry(LocalDateTime.now().plusHours(1));
            userRepository.save(user);

            logger.info("Password reset initiated for user: {}", email);
            return true;
        } catch (Exception e) {
            logger.error("Error initiating password reset for: {}", email, e);
            return false;
        }
    }

    @Transactional
    public String generateResetToken(String email) {
        Optional<User> userOpt = userRepository.findByEmail(email);
        if (userOpt.isEmpty()) {
            throw new RuntimeException("Användare med email " + email + " hittades inte");
        }

        User user = userOpt.get();

        String resetToken = UUID.randomUUID().toString();
        user.setResetToken(resetToken);
        user.setResetTokenExpiry(LocalDateTime.now().plusHours(24));

        userRepository.save(user);

        logger.info("Reset token generated for user: {}", email);
        return resetToken;
    }

    public boolean isValidResetToken(String token) {
        if (token == null || token.isEmpty()) {
            return false;
        }

        Optional<User> userOpt = userRepository.findByResetToken(token);
        if (userOpt.isEmpty()) {
            return false;
        }

        return userOpt.get().isResetTokenValid();
    }

    @Transactional
    public boolean resetPassword(String token, String newPassword) {
        Optional<User> userOpt = userRepository.findByResetToken(token);
        if (userOpt.isEmpty()) {
            logger.warn("Invalid reset token: {}", token);
            return false;
        }

        User user = userOpt.get();
        if (!user.isResetTokenValid()) {
            logger.warn("Reset token expired for user: {}", user.getEmail());
            return false;
        }

        user.setPassword(passwordEncoder.encode(newPassword));
        user.setResetToken(null);
        user.setResetTokenExpiry(null);
        userRepository.save(user);

        logger.info("Password reset successfully for user: {}", user.getEmail());
        return true;
    }

    // ===== SPRING SECURITY INTEGRATION =====

    public UserDetails loadUserByUsername(String username) {
        return userRepository.findByUsernameAndActiveTrue(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
    }

    // ===== ADMIN AND ROLE MANAGEMENT =====

    public boolean isUserAdmin(long userId) {
        return userRepository.findById(userId)
                .map(user -> {
                    if ("admin".equals(user.getUsername())) {
                        return true;
                    }

                    if (user.getRoles() != null) {
                        String roles = user.getRoles().toString().toLowerCase();
                        return roles.contains("admin") || roles.contains("role_admin");
                    }

                    return false;
                })
                .orElse(false);
    }

    public boolean isUserAdmin(String username) {
        return userRepository.findByUsername(username)
                .map(user -> {
                    if ("admin".equals(username) || "admin".equals(user.getUsername())) {
                        return true;
                    }

                    if (user.getRoles() != null) {
                        String roles = user.getRoles().toString().toLowerCase();
                        return roles.contains("admin") || roles.contains("role_admin");
                    }

                    return false;
                })
                .orElse(false);
    }

    // ===== UTILITY METHODS =====

    public String getFirstNameByEmail(String email) {
        return userRepository.findByEmail(email)
                .map(User::getFirstName)
                .orElse("Användare");
    }

    public UserStats getUserStats() {
        long totalUsers = userRepository.count();
        long activeUsers = userRepository.countByActiveTrue();
        long verifiedUsers = userRepository.countByEmailVerifiedTrue();

        return new UserStats(totalUsers, activeUsers, verifiedUsers);
    }

    // ===== INNER CLASSES =====

    /**
     * UserStats class for providing user statistics
     */
    public static class UserStats {
        private final long totalUsers;
        private final long activeUsers;
        private final long verifiedUsers;
        private final long inactiveUsers;
        private final long unverifiedUsers;

        public UserStats(long totalUsers, long activeUsers, long verifiedUsers) {
            this.totalUsers = totalUsers;
            this.activeUsers = activeUsers;
            this.verifiedUsers = verifiedUsers;
            this.inactiveUsers = totalUsers - activeUsers;
            this.unverifiedUsers = totalUsers - verifiedUsers;
        }

        // Standard getters
        public long getTotalUsers() { return totalUsers; }
        public long getActiveUsers() { return activeUsers; }
        public long getVerifiedUsers() { return verifiedUsers; }
        public long getInactiveUsers() { return inactiveUsers; }
        public long getUnverifiedUsers() { return unverifiedUsers; }

        // Alternative method names for compatibility
        public long totalUsers() { return totalUsers; }
        public long activeUsers() { return activeUsers; }
        public long verifiedUsers() { return verifiedUsers; }
        public long inactiveUsers() { return inactiveUsers; }
        public long unverifiedUsers() { return unverifiedUsers; }

        @Override
        public String toString() {
            return "UserStats{totalUsers=" + totalUsers +
                    ", activeUsers=" + activeUsers +
                    ", verifiedUsers=" + verifiedUsers +
                    ", inactiveUsers=" + inactiveUsers +
                    ", unverifiedUsers=" + unverifiedUsers + "}";
        }
    }
}