package promotion

import (
	"time"

	"github.com/pingcap/dbaas/billing2/config"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/utils/log"
	"go.uber.org/zap"
)

// RewardNewUserSignup triggers referee_signup reward for the new user (B)
// when they register with a referral code.
func RewardNewUserSignup(referralCode string, tenantID uint) error {
	if referralCode == "" {
		log.Info("promotion: RewardNewUserSignup skipped, no referral code",
			zap.Uint("tenantID", tenantID))
		return nil
	}
	log.Info("promotion: RewardNewUserSignup called",
		zap.String("referralCode", referralCode),
		zap.Uint("tenantID", tenantID))

	amounts := getRewardAmounts()

	if !referralModel.HasEvent(tenantID, entity.ReferralEventType_RefereeSignup) {
		resp, err := referralModel.TriggerRefereeReward(&forms.ReferralRewardReq{
			ReferralCode:    referralCode,
			RefereeTenantID: tenantID,
			EventType:       entity.ReferralEventType_RefereeSignup,
			CreditsAmount:   amounts.RefereeSignup,
		})
		if err != nil {
			log.Error("promotion: RewardNewUserSignup referee reward failed",
				zap.String("referralCode", referralCode),
				zap.Uint("tenantID", tenantID),
				zap.Error(err))
			return err
		}
		log.Info("promotion: RewardNewUserSignup referee reward done",
			zap.String("referralCode", referralCode),
			zap.Uint("tenantID", tenantID),
			zap.Bool("rewarded", resp.Rewarded),
			zap.String("skipReason", resp.SkipReason))
	} else {
		log.Info("promotion: RewardNewUserSignup referee reward skipped, event already exists",
			zap.Uint("tenantID", tenantID))
	}

	return nil
}

// RewardNewUserBindCard triggers referee_bindcard reward for the new user (B)
// when they bind a credit card.
func RewardNewUserBindCard(tenantID uint) error {
	log.Info("promotion: RewardNewUserBindCard called",
		zap.Uint("tenantID", tenantID))

	code, err := referralModel.FindReferralCodeBySignupEvent(tenantID)
	if err != nil {
		log.Info("promotion: RewardNewUserBindCard skipped, not a referred user",
			zap.Uint("tenantID", tenantID), zap.Error(err))
		return nil
	}

	amounts := getRewardAmounts()

	if !referralModel.HasEvent(tenantID, entity.ReferralEventType_RefereeBindCard) {
		resp, err := referralModel.TriggerRefereeReward(&forms.ReferralRewardReq{
			ReferralCode:    code,
			RefereeTenantID: tenantID,
			EventType:       entity.ReferralEventType_RefereeBindCard,
			CreditsAmount:   amounts.RefereeBindCard,
		})
		if err != nil {
			log.Error("promotion: RewardNewUserBindCard referee reward failed",
				zap.Uint("tenantID", tenantID),
				zap.String("referralCode", code),
				zap.Error(err))
			return err
		}
		log.Info("promotion: RewardNewUserBindCard referee reward done",
			zap.Uint("tenantID", tenantID),
			zap.Bool("rewarded", resp.Rewarded),
			zap.String("skipReason", resp.SkipReason))
	} else {
		log.Info("promotion: RewardNewUserBindCard referee reward skipped, event already exists",
			zap.Uint("tenantID", tenantID))
	}

	return nil
}

// RewardFirstUse finds tenants who signed up but haven't received first_use reward,
// then checks if they had >= threshold requests in the last hour.
func RewardFirstUse() {
	log.Info("promotion: RewardFirstUse checker started")

	threshold := config.Get().GetInt(config.PromotionFirstUseThreshold)

	tenantIDs, err := referralModel.FindTenantsMissingEvent(entity.ReferralEventType_Signup, entity.ReferralEventType_FirstUse)
	if err != nil {
		log.Error("promotion: failed to find tenants for first_use check", zap.Error(err))
		return
	}
	if len(tenantIDs) == 0 {
		log.Info("promotion: RewardFirstUse no candidates found")
		return
	}

	amounts := getRewardAmounts()

	log.Info("promotion: RewardFirstUse checking candidates",
		zap.Int("candidates", len(tenantIDs)), zap.Int("threshold", threshold))
	now := time.Now()
	start := now.Add(-1 * time.Hour)

	for _, tenantID := range tenantIDs {
		totalRequests, err := referralModel.QueryTenantQPS(tenantID, start, now)
		if err != nil {
			log.Warn("promotion: RewardFirstUse failed to query QPS",
				zap.Uint("tenantID", tenantID), zap.Error(err))
			continue
		}
		if totalRequests >= float64(threshold) {
			log.Info("promotion: RewardFirstUse tenant qualifies",
				zap.Uint("tenantID", tenantID),
				zap.Float64("totalRequests", totalRequests),
				zap.Int("threshold", threshold))
			referralModel.TriggerSelfReward(tenantID, entity.ReferralEventType_FirstUse, amounts.FirstUse)
		} else {
			log.Info("promotion: RewardFirstUse tenant below threshold",
				zap.Uint("tenantID", tenantID),
				zap.Float64("totalRequests", totalRequests),
				zap.Int("threshold", threshold))
		}
	}

	log.Info("promotion: RewardFirstUse checker finished", zap.Int("candidates", len(tenantIDs)))
}

// RewardContinuousUse finds tenants who got first_use reward but haven't received continuous_use reward,
// then checks if they had >= threshold requests per day for the required consecutive days.
// After granting continuous_use, also triggers referee_continuous_use for the referrer.
func RewardContinuousUse() {
	log.Info("promotion: RewardContinuousUse checker started")

	days := config.Get().GetInt(config.PromotionContinuousUseDays)
	dailyThreshold := config.Get().GetInt(config.PromotionContinuousUseDaily)

	tenantIDs, err := referralModel.FindTenantsMissingEvent(entity.ReferralEventType_FirstUse, entity.ReferralEventType_ContinuousUse)
	if err != nil {
		log.Error("promotion: failed to find tenants for continuous_use check", zap.Error(err))
		return
	}
	if len(tenantIDs) == 0 {
		log.Info("promotion: RewardContinuousUse no candidates found")
		return
	}

	amounts := getRewardAmounts()

	log.Info("promotion: RewardContinuousUse checking candidates",
		zap.Int("candidates", len(tenantIDs)),
		zap.Int("days", days), zap.Int("dailyThreshold", dailyThreshold))

	for _, tenantID := range tenantIDs {
		if referralModel.CheckConsecutiveDays(tenantID, days, dailyThreshold) {
			log.Info("promotion: RewardContinuousUse tenant qualifies",
				zap.Uint("tenantID", tenantID))
			referralModel.TriggerSelfReward(tenantID, entity.ReferralEventType_ContinuousUse, amounts.ContinuousUse)
			// Also trigger referee_continuous_use reward for the referrer
			RewardReferralContinuousUse(tenantID)
		} else {
			log.Info("promotion: RewardContinuousUse tenant not qualified yet",
				zap.Uint("tenantID", tenantID))
		}
	}

	log.Info("promotion: RewardContinuousUse checker finished", zap.Int("candidates", len(tenantIDs)))
}
