package com.honey.honey.service;

import com.honey.honey.model.Promotion;
import com.honey.honey.model.Promotion.PromotionType;
import com.honey.honey.model.PromotionUser;
import com.honey.honey.repository.PromotionRepository;
import com.honey.honey.repository.PromotionUserRepository;
import com.honey.honey.repository.UserDRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class PromotionService {

    /** 1 ticket = 1_000_000 in balance/bet storage; points are stored as ticket count with 2 decimals. */
    private static final long TICKETS_MULTIPLIER = 1_000_000L;

    private final PromotionRepository promotionRepository;
    private final PromotionUserRepository promotionUserRepository;
    private final UserDRepository userDRepository;

    /**
     * Add net win points to the user for all currently active NET_WIN promotions.
     * Called when a user wins a round: netWinBigint = payout - winnerBet (in bigint).
     * Points are stored as ticket count with 2 decimal places (e.g. 82.25 tickets → points += 82.25).
     */
    @Transactional
    public void addNetWinPoints(int userId, long netWinBigint) {
        if (netWinBigint <= 0) {
            return;
        }
        BigDecimal pointsToAdd = BigDecimal.valueOf(netWinBigint)
                .divide(BigDecimal.valueOf(TICKETS_MULTIPLIER), 2, RoundingMode.HALF_UP);
        if (pointsToAdd.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        Instant now = Instant.now();
        List<Promotion> activePromos = promotionRepository.findActiveByTypeAndTimeRange(PromotionType.NET_WIN, now);
        for (Promotion promo : activePromos) {
            addPointsToPromoUser(promo.getId(), userId, pointsToAdd, now);
        }
    }

    /**
     * Add net win points for all active NET_WIN_MAX_BET promotions (only when winner made max bet in the room).
     * Same points logic as NET_WIN: netWinBigint = payout - winnerBet.
     */
    @Transactional
    public void addNetWinMaxBetPoints(int userId, long netWinBigint) {
        if (netWinBigint <= 0) {
            return;
        }
        BigDecimal pointsToAdd = BigDecimal.valueOf(netWinBigint)
                .divide(BigDecimal.valueOf(TICKETS_MULTIPLIER), 2, RoundingMode.HALF_UP);
        if (pointsToAdd.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        Instant now = Instant.now();
        List<Promotion> activePromos = promotionRepository.findActiveByTypeAndTimeRange(PromotionType.NET_WIN_MAX_BET, now);
        for (Promotion promo : activePromos) {
            addPointsToPromoUser(promo.getId(), userId, pointsToAdd, now);
        }
    }

    /**
     * Add 1 point for the referer (level 1) in active REF_COUNT promotions where the referral's
     * registration date falls within the promotion timeframe.
     * Called when a referral completes their first round (rounds_played was 0 before this round).
     * Only promotions for which referralRegistrationTime is between startTime and endTime (inclusive) get the point.
     * Masters (users where userId = masterId) are excluded and never receive REF_COUNT points.
     */
    @Transactional
    public void addRefCountPoints(Integer refererUserId, Instant referralRegistrationTime) {
        if (refererUserId == null || refererUserId <= 0 || referralRegistrationTime == null) {
            return;
        }
        // Exclude masters: do not add REF_COUNT points for referrers who are masters (id = masterId and masterId > 0)
        if (userDRepository.findById(refererUserId)
                .filter(ud -> ud.getMasterId() != null && ud.getMasterId() > 0 && refererUserId.equals(ud.getMasterId()))
                .isPresent()) {
            return;
        }
        BigDecimal onePoint = BigDecimal.ONE;
        Instant now = Instant.now();
        List<Promotion> activePromos = promotionRepository.findActiveByTypeAndTimeRange(PromotionType.REF_COUNT, now);
        for (Promotion promo : activePromos) {
            // Only add point if the referral was registered during this promotion's timeframe
            if (!referralRegistrationTime.isBefore(promo.getStartTime()) && !referralRegistrationTime.isAfter(promo.getEndTime())) {
                addPointsToPromoUser(promo.getId(), refererUserId, onePoint, now);
            }
        }
    }

    private void addPointsToPromoUser(int promoId, int userId, BigDecimal pointsToAdd, Instant now) {
        promotionUserRepository.findByPromoIdAndUserId(promoId, userId)
                .ifPresentOrElse(
                        pu -> {
                            pu.setPoints(pu.getPoints().add(pointsToAdd));
                            promotionUserRepository.save(pu);
                        },
                        () -> {
                            PromotionUser newPu = PromotionUser.builder()
                                    .promoId(promoId)
                                    .userId(userId)
                                    .points(pointsToAdd)
                                    .updatedAt(now)
                                    .build();
                            promotionUserRepository.save(newPu);
                        }
                );
    }
}
