LikeCommandService.java

package com.newbit.like.service;

import java.util.Optional;

import com.newbit.notification.command.application.dto.request.NotificationSendRequest;
import com.newbit.notification.command.application.service.NotificationCommandService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.newbit.common.exception.BusinessException;
import com.newbit.common.exception.ErrorCode;
import com.newbit.column.domain.Column;
import com.newbit.column.repository.ColumnRepository;
import com.newbit.like.dto.response.ColumnLikeResponse;
import com.newbit.like.dto.response.PostLikeResponse;
import com.newbit.like.entity.Like;
import com.newbit.like.repository.LikeRepository;
import com.newbit.post.entity.Post;
import com.newbit.post.repository.PostRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Service
@RequiredArgsConstructor
public class LikeCommandService {

    private final LikeRepository likeRepository;
    private final PostRepository postRepository;
    private final ColumnRepository columnRepository;
    private final PointRewardService pointRewardService;
    private final NotificationCommandService notificationCommandService;

    @Transactional
    public PostLikeResponse togglePostLike(Long postId, Long userId) {
        try {
            Post post = findPostById(postId);
            Optional<Like> existingLike = findExistingPostLike(postId, userId);
            
            if (existingLike.isPresent()) {
                return unlikePost(existingLike.get(), post);
            } else {
                return likePost(postId, userId, post);
            }
        } catch (BusinessException e) {
            log.error("좋아요 처리 중 비즈니스 예외 발생: postId={}, userId={}, errorCode={}, message={}",
                    postId, userId, e.getErrorCode(), e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("좋아요 처리 중 예기치 않은 오류 발생: postId={}, userId={}, error={}", 
                    postId, userId, e.getMessage(), e);
            throw new BusinessException(ErrorCode.LIKE_PROCESSING_ERROR);
        }
    }
    
    @Transactional
    public ColumnLikeResponse toggleColumnLike(Long columnId, Long userId) {
        try {
            Column column = findColumnById(columnId);
            Optional<Like> existingLike = findExistingColumnLike(columnId, userId);
            
            if (existingLike.isPresent()) {
                return unlikeColumn(existingLike.get(), column);
            } else {
                return likeColumn(columnId, userId, column);
            }
        } catch (BusinessException e) {
            log.error("칼럼 좋아요 처리 중 비즈니스 예외 발생: columnId={}, userId={}, errorCode={}, message={}",
                    columnId, userId, e.getErrorCode(), e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error("칼럼 좋아요 처리 중 예기치 않은 오류 발생: columnId={}, userId={}, error={}", 
                    columnId, userId, e.getMessage(), e);
            throw new BusinessException(ErrorCode.LIKE_PROCESSING_ERROR);
        }
    }

    private Post findPostById(Long postId) {
        return postRepository.findByIdAndDeletedAtIsNull(postId)
                .orElseThrow(() -> {
                    log.warn("좋아요 처리를 위한 게시글을 찾을 수 없음: postId={}", postId);
                    return new BusinessException(ErrorCode.POST_LIKE_NOT_FOUND);
                });
    }

    private Optional<Like> findExistingPostLike(Long postId, Long userId) {
        return likeRepository.findByPostIdAndUserIdAndIsDeleteFalse(postId, userId);
    }

    private PostLikeResponse unlikePost(Like like, Post post) {
        try {
            like.setDelete(true);
            likeRepository.save(like);
            
            decreaseLikeCount(post);
            
            return PostLikeResponse.unliked(post.getId(), like.getUserId(), post.getLikeCount());
        } catch (Exception e) {
            log.error("좋아요 취소 처리 중 오류 발생: postId={}, userId={}, error={}", 
                    post.getId(), like.getUserId(), e.getMessage(), e);
            throw new BusinessException(ErrorCode.LIKE_PROCESSING_ERROR);
        }
    }

    private PostLikeResponse likePost(Long postId, Long userId, Post post) {
        try {
            Like like = createPostLike(postId, userId);
            likeRepository.save(like);
            
            increaseLikeCount(post);
            
            pointRewardService.givePointIfFirstLike(postId, userId, post.getUserId());

            if(isFibonacci(post.getLikeCount())){
                String notificationContent = String.format("'%s' 게시글이 좋아요를 받았습니다. (총 %d개)",
                        post.getTitle(), post.getLikeCount());

                notificationCommandService.sendNotification(
                        new NotificationSendRequest(
                                post.getUserId()
                                , 2L // 예: 좋아요 알림 유형 ID
                                , postId,
                                notificationContent
                        )
                );
            }

            
            return PostLikeResponse.of(like, post.getLikeCount());
        } catch (Exception e) {
            log.error("좋아요 추가 처리 중 오류 발생: postId={}, userId={}, error={}", 
                    postId, userId, e.getMessage(), e);
            throw new BusinessException(ErrorCode.LIKE_PROCESSING_ERROR);
        }
    }

    private Like createPostLike(Long postId, Long userId) {
        return Like.builder()
                .postId(postId)
                .userId(userId)
                .isDelete(false)
                .build();
    }

    private void decreaseLikeCount(Post post) {
        post.setLikeCount(Math.max(0, post.getLikeCount() - 1));
        postRepository.save(post);
    }

    private void increaseLikeCount(Post post) {
        post.setLikeCount(post.getLikeCount() + 1);
        postRepository.save(post);
    }
    
    private Column findColumnById(Long columnId) {
        return columnRepository.findById(columnId)
                .orElseThrow(() -> {
                    log.warn("좋아요 처리를 위한 칼럼을 찾을 수 없음: columnId={}", columnId);
                    return new BusinessException(ErrorCode.COLUMN_NOT_FOUND);
                });
    }
    
    private Optional<Like> findExistingColumnLike(Long columnId, Long userId) {
        return likeRepository.findByColumnIdAndUserIdAndIsDeleteFalse(columnId, userId);
    }
    
    private ColumnLikeResponse unlikeColumn(Like like, Column column) {
        try {
            like.setDelete(true);
            likeRepository.save(like);
            
            column.decreaseLikeCount();
            columnRepository.save(column);
            
            return ColumnLikeResponse.unliked(column.getColumnId(), like.getUserId(), column.getLikeCount());
        } catch (Exception e) {
            log.error("칼럼 좋아요 취소 처리 중 오류 발생: columnId={}, userId={}, error={}", 
                    column.getColumnId(), like.getUserId(), e.getMessage(), e);
            throw new BusinessException(ErrorCode.LIKE_PROCESSING_ERROR);
        }
    }
    
    private ColumnLikeResponse likeColumn(Long columnId, Long userId, Column column) {
        try {
            Like like = createColumnLike(columnId, userId);
            likeRepository.save(like);
            
            column.increaseLikeCount();
            columnRepository.save(column);

            if(isFibonacci(column.getLikeCount())){
                String notificationContent = String.format("'%s' 칼럼이 좋아요를 받았습니다. (총 %d개)",
                        column.getTitle(), column.getLikeCount());

                notificationCommandService.sendNotification(
                        new NotificationSendRequest(
                                column.getMentor().getUser().getUserId()
                                , 2L // 예: 좋아요 알림 유형 ID
                                , columnId,
                                notificationContent
                        )
                );
            }


            return ColumnLikeResponse.of(like, column.getLikeCount());
        } catch (Exception e) {
            log.error("칼럼 좋아요 추가 처리 중 오류 발생: columnId={}, userId={}, error={}", 
                    columnId, userId, e.getMessage(), e);
            throw new BusinessException(ErrorCode.LIKE_PROCESSING_ERROR);
        }
    }

    private boolean isFibonacci(int n) {
        int a = 0, b = 1;
        while (b < n) {
            int temp = b;
            b = a + b;
            a = temp;
        }
        return b == n;
    }


    private Like createColumnLike(Long columnId, Long userId) {
        return Like.builder()
                .columnId(columnId)
                .userId(userId)
                .isDelete(false)
                .build();
    }
}