AchievementService.java

package org.petify.backend.services;

import org.petify.backend.models.Achievement;
import org.petify.backend.models.AchievementCategory;
import org.petify.backend.models.ApplicationUser;
import org.petify.backend.models.UserAchievement;
import org.petify.backend.repository.AchievementRepository;
import org.petify.backend.repository.UserAchievementRepository;
import org.petify.backend.repository.UserRepository;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Service
@Slf4j
public class AchievementService {

    @Autowired
    private AchievementRepository achievementRepository;

    @Autowired
    private UserAchievementRepository userAchievementRepository;

    @Autowired
    private UserRepository userRepository;

    @Transactional(readOnly = true)
    public List<UserAchievement> getUserAchievements(String username) {
        ApplicationUser user = userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("User not found"));

        return userAchievementRepository.findByUser(user);
    }

    @Transactional
    public UserAchievement trackAchievementProgress(String username, Long achievementId, int progressIncrement) {
        ApplicationUser user = userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("User not found"));

        Achievement achievement = achievementRepository.findById(achievementId)
                .orElseThrow(() -> new RuntimeException("Achievement not found"));

        UserAchievement userAchievement = userAchievementRepository
                .findByUserAndAchievementId(user, achievementId)
                .orElseGet(() -> {
                    UserAchievement newAchievement = new UserAchievement();
                    newAchievement.setUser(user);
                    newAchievement.setAchievement(achievement);
                    newAchievement.setCurrentProgress(0);
                    return newAchievement;
                });

        if (userAchievement.getCompleted()) {
            return userAchievement;
        }

        int newProgress = userAchievement.getCurrentProgress() + progressIncrement;
        userAchievement.setCurrentProgress(newProgress);

        if (newProgress >= achievement.getRequiredActions()) {
            userAchievement.setCompleted(true);
            userAchievement.setCompletionDate(LocalDateTime.now());

            user.setXpPoints(user.getXpPoints() + achievement.getXpReward());

            updateBadgeCounts(user, achievement.getCategory());

            updateUserLevel(user);

            userRepository.save(user);

            log.info("User {} completed achievement: {} (+{} XP)",
                    username, achievement.getName(), achievement.getXpReward());
        }

        return userAchievementRepository.save(userAchievement);
    }

    @Transactional
    public void trackLikeAchievements(String username) {
        ApplicationUser user = userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("User not found"));

        user.setLikesCount(user.getLikesCount() + 1);
        userRepository.save(user);

        List<Achievement> likeAchievements = achievementRepository.findByCategory(AchievementCategory.LIKES);

        for (Achievement achievement : likeAchievements) {
            trackAchievementProgress(username, achievement.getId(), 1);
        }
        log.info("Tracked like achievements for user {} - new like count: {}",
                username, user.getLikesCount());
    }

    @Transactional
    public void trackSupportAchievements(String username) {
        ApplicationUser user = userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("User not found"));

        user.setSupportCount(user.getSupportCount() + 1);
        userRepository.save(user);

        List<Achievement> supportAchievements = achievementRepository.findByCategory(AchievementCategory.SUPPORT);

        for (Achievement achievement : supportAchievements) {
            trackAchievementProgress(username, achievement.getId(), 1);
        }

        log.info("Tracked support achievements for user {} - new support count: {}",
                username, user.getSupportCount());
    }

    @Transactional
    public void trackProfileAchievementByName(String username, String achievementName) {
        trackAchievementByNameAndCategory(username, achievementName, AchievementCategory.PROFILE);
    }

    private void updateBadgeCounts(ApplicationUser user, AchievementCategory category) {
        switch (category) {
            case LIKES:
                break;
            case SUPPORT:
                break;
            case ADOPTION:
                user.setAdoptionCount(user.getAdoptionCount() + 1);
                user.setBadgesCount(user.getBadgesCount() + 1);
                break;
            case BADGE:
            case PROFILE:
            case VOLUNTEER:
                user.setBadgesCount(user.getBadgesCount() + 1);
                break;
            default:
                break;
        }
    }

    private void updateUserLevel(ApplicationUser user) {
        int xp = user.getXpPoints();
        int newLevel = (xp / 100) + 1;

        if (newLevel > user.getLevel()) {
            int oldLevel = user.getLevel();
            user.setLevel(newLevel);
            log.info("User {} leveled up from {} to {}! (Total XP: {})",
                    user.getUsername(), oldLevel, newLevel, xp);
        }
    }

    public Map<String, Object> getUserLevelInfo(String username) {
        ApplicationUser user = userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("User not found"));

        Map<String, Object> levelInfo = new HashMap<>();
        levelInfo.put("level", user.getLevel());
        levelInfo.put("xpPoints", user.getXpPoints());
        levelInfo.put("xpToNextLevel", user.getXpToNextLevel());
        levelInfo.put("likesCount", user.getLikesCount());
        levelInfo.put("supportCount", user.getSupportCount());
        levelInfo.put("badgesCount", user.getBadgesCount());
        levelInfo.put("adoptionCount", user.getAdoptionCount());

        return levelInfo;
    }

    @Transactional
    public void initializeUserAchievements(ApplicationUser user) {
        List<Achievement> allAchievements = achievementRepository.findAll();

        for (Achievement achievement : allAchievements) {
            UserAchievement userAchievement = new UserAchievement();
            userAchievement.setUser(user);
            userAchievement.setAchievement(achievement);
            userAchievement.setCurrentProgress(0);
            userAchievement.setCompleted(false);

            userAchievementRepository.save(userAchievement);
        }
    }

    @Transactional
    public void trackVolunteerAchievements(String username) {
        List<Achievement> volunteerAchievements = achievementRepository.findByCategory(AchievementCategory.VOLUNTEER);

        for (Achievement achievement : volunteerAchievements) {
            trackAchievementProgress(username, achievement.getId(), 1);
        }
    }

    @Transactional
    public void trackVolunteerAchievementByName(String username, String achievementName) {
        trackAchievementByNameAndCategory(username, achievementName, AchievementCategory.VOLUNTEER);
    }

    private void trackAchievementByNameAndCategory(String username, String achievementName, AchievementCategory category) {
        try {
            List<Achievement> achievements = achievementRepository.findByCategory(category);

            Optional<Achievement> achievement = achievements.stream()
                    .filter(a -> a.getName().equals(achievementName))
                    .findFirst();

            if (achievement.isPresent()) {
                ApplicationUser user = userRepository.findByUsername(username)
                        .orElseThrow(() -> new RuntimeException("User not found"));

                Optional<UserAchievement> existingUserAchievement = userAchievementRepository
                        .findByUserAndAchievementId(user, achievement.get().getId());

                if (existingUserAchievement.isEmpty() || !existingUserAchievement.get().getCompleted()) {
                    trackAchievementProgress(username, achievement.get().getId(), 1);
                }
            } else {
                log.warn("Achievement with name '{}' not found in {} category", achievementName, category);
            }
        } catch (Exception e) {
            log.error("Error tracking {} achievement '{}' for user {}: {}",
                    category, achievementName, username, e.getMessage());
        }
    }

    @Transactional
    public void trackAdoptionAchievements(String username) {
        List<Achievement> adoptionAchievements = achievementRepository.findByCategory(AchievementCategory.ADOPTION);

        for (Achievement achievement : adoptionAchievements) {
            trackAchievementProgress(username, achievement.getId(), 1);
        }
    }

    @Transactional
    public void addExperiencePointsForDonation(String username, int xpPoints) {
        ApplicationUser user = userRepository.findByUsername(username)
                .orElseThrow(() -> new RuntimeException("User not found"));

        user.setXpPoints(user.getXpPoints() + xpPoints);
        updateUserLevel(user);
        userRepository.save(user);

        log.info("Added {} XP to user {} for donation (Total XP: {})", 
                xpPoints, username, user.getXpPoints());
    }
}