package com.honey.honey.service;

import com.honey.honey.dto.PromotionDetailDto;
import com.honey.honey.dto.PromotionLeaderboardEntryDto;
import com.honey.honey.dto.PromotionListItemDto;
import com.honey.honey.model.Promotion;
import com.honey.honey.model.Promotion.PromotionStatus;
import com.honey.honey.model.PromotionReward;
import com.honey.honey.model.PromotionUser;
import com.honey.honey.model.UserA;
import com.honey.honey.repository.PromotionRepository;
import com.honey.honey.repository.PromotionRewardRepository;
import com.honey.honey.repository.PromotionUserRepository;
import com.honey.honey.repository.UserARepository;
import com.honey.honey.security.UserContext;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class PublicPromotionService {

    private static final int LEADERBOARD_SIZE = 30;
    private static final long TICKETS_MULTIPLIER = 1_000_000L;

    private final PromotionRepository promotionRepository;
    private final PromotionUserRepository promotionUserRepository;
    private final PromotionRewardRepository promotionRewardRepository;
    private final UserARepository userARepository;

    /**
     * List promotions for app: ACTIVE and FINISHED only (exclude INACTIVE and PLANNED).
     */
    @Transactional(readOnly = true)
    public List<PromotionListItemDto> listForApp() {
        List<PromotionStatus> allowed = List.of(PromotionStatus.ACTIVE, PromotionStatus.FINISHED);
        List<Promotion> list = promotionRepository.findByStatusInOrderByStartTimeDesc(allowed);
        return list.stream().map(this::toListItemDto).collect(Collectors.toList());
    }

    /**
     * Get promotion detail for app. Returns empty if not found or status is INACTIVE or PLANNED.
     */
    @Transactional(readOnly = true)
    public Optional<PromotionDetailDto> getDetailForApp(int promotionId) {
        Optional<Promotion> promoOpt = promotionRepository.findById(promotionId);
        if (promoOpt.isEmpty()) {
            return Optional.empty();
        }
        Promotion promo = promoOpt.get();
        if (promo.getStatus() == PromotionStatus.INACTIVE || promo.getStatus() == PromotionStatus.PLANNED) {
            return Optional.empty();
        }

        int userId = UserContext.get().getId();

        List<PromotionUser> topUsers = promotionUserRepository.findByPromoId(
                promo.getId(),
                PageRequest.of(0, LEADERBOARD_SIZE, Sort.by(Sort.Direction.DESC, "points"))
        ).getContent();

        List<PromotionReward> rewards = promotionRewardRepository.findByPromotionIdOrderByPlaceAsc(promo.getId());
        Map<Integer, Long> rewardTicketsByPlace = rewards.stream()
                .collect(Collectors.toMap(PromotionReward::getPlace, r -> r.getReward() / TICKETS_MULTIPLIER));

        List<Integer> userIds = topUsers.stream().map(PromotionUser::getUserId).distinct().toList();
        Map<Integer, String> screenNameByUserId = userARepository.findAllById(userIds).stream()
                .collect(Collectors.toMap(UserA::getId, u -> u.getScreenName() != null ? u.getScreenName() : "-"));

        List<PromotionLeaderboardEntryDto> leaderboard = new ArrayList<>();
        for (int i = 0; i < LEADERBOARD_SIZE; i++) {
            int place = i + 1;
            String screenName = "-";
            BigDecimal points = BigDecimal.ZERO;
            Long rewardTickets = rewardTicketsByPlace.get(place);
            if (i < topUsers.size()) {
                PromotionUser pu = topUsers.get(i);
                screenName = screenNameByUserId.getOrDefault(pu.getUserId(), "-");
                points = pu.getPoints() != null ? pu.getPoints() : BigDecimal.ZERO;
            }
            leaderboard.add(PromotionLeaderboardEntryDto.builder()
                    .place(place)
                    .screenName(screenName)
                    .points(points)
                    .rewardTickets(rewardTickets)
                    .build());
        }

        int totalParticipants = (int) promotionUserRepository.countByPromoId(promo.getId());
        Optional<PromotionUser> currentUserEntry = promotionUserRepository.findByPromoIdAndUserId(promo.getId(), userId);
        BigDecimal userPoints = currentUserEntry.map(pu -> pu.getPoints() != null ? pu.getPoints() : BigDecimal.ZERO).orElse(BigDecimal.ZERO);

        int userPosition = 0;
        if (currentUserEntry.isPresent()) {
            BigDecimal myPoints = currentUserEntry.get().getPoints();
            long countBetter = promotionUserRepository.countByPromoIdAndPointsGreaterThan(promo.getId(), myPoints != null ? myPoints : BigDecimal.ZERO);
            userPosition = (int) countBetter + 1;
        }

        PromotionDetailDto dto = PromotionDetailDto.builder()
                .id(promo.getId())
                .type(promo.getType().name())
                .status(promo.getStatus().name())
                .startTime(promo.getStartTime())
                .endTime(promo.getEndTime())
                .totalReward(promo.getTotalReward())
                .leaderboard(leaderboard)
                .userPosition(userPosition)
                .userTotal(totalParticipants)
                .userPoints(userPoints)
                .build();
        return Optional.of(dto);
    }

    private PromotionListItemDto toListItemDto(Promotion p) {
        return PromotionListItemDto.builder()
                .id(p.getId())
                .type(p.getType().name())
                .status(p.getStatus().name())
                .startTime(p.getStartTime())
                .endTime(p.getEndTime())
                .totalReward(p.getTotalReward())
                .build();
    }
}
