package referralservice

import (
	"context"
	"crypto/rand"
	"encoding/base32"
	"errors"
	"log/slog"
	"strconv"
	"time"

	"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
	"github.com/SamuelTariku/FortuneBet-Backend/internal/services/wallet"
)

type Service struct {
	repo      repository.ReferralRepository
	walletSvc wallet.Service
	store     *repository.Store
	config    *config.Config
	logger    *slog.Logger
}

func New(repo repository.ReferralRepository, walletSvc wallet.Service, store *repository.Store, cfg *config.Config, logger *slog.Logger) *Service {
	return &Service{
		repo:      repo,
		walletSvc: walletSvc,
		store:     store,
		config:    cfg,
		logger:    logger,
	}
}

var (
	ErrInvalidReferral       = errors.New("invalid or expired referral")
	ErrInvalidReferralSignup = errors.New("referral requires phone signup")
	ErrUserNotFound          = errors.New("user not found")
	ErrNoReferralFound       = errors.New("no referral found for this user")
)

func (s *Service) GenerateReferralCode() (string, error) {
	b := make([]byte, 8)
	if _, err := rand.Read(b); err != nil {
		s.logger.Error("Failed to generate random bytes for referral code", "error", err)
		return "", err
	}
	code := base32.StdEncoding.EncodeToString(b)[:10]
	s.logger.Debug("Generated referral code", "code", code)
	return code, nil
}

func (s *Service) CreateReferral(ctx context.Context, userID int64) error {
	s.logger.Info("Creating referral code for user", "userID", userID)
	code, err := s.GenerateReferralCode()
	if err != nil {
		s.logger.Error("Failed to generate referral code", "error", err)
		return err
	}

	if err := s.repo.UpdateUserReferalCode(ctx, domain.UpdateUserReferalCode{
		UserID: userID,
		Code:   code,
	}); err != nil {
		return err
	}

	return nil
}

func (s *Service) ProcessReferral(ctx context.Context, referredPhone, referralCode string) error {
	s.logger.Info("Processing referral", "referredPhone", referredPhone, "referralCode", referralCode)

	referral, err := s.repo.GetReferralByCode(ctx, referralCode)
	if err != nil {
		s.logger.Error("Failed to get referral by code", "referralCode", referralCode, "error", err)
		return err
	}

	if referral == nil || referral.Status != domain.ReferralPending || referral.ExpiresAt.Before(time.Now()) {
		s.logger.Warn("Invalid or expired referral", "referralCode", referralCode, "status", referral.Status)
		return ErrInvalidReferral
	}

	user, err := s.store.GetUserByPhone(ctx, referredPhone)
	if err != nil {
		if errors.Is(err, domain.ErrUserNotFound) {
			s.logger.Warn("User not found for referral", "referredPhone", referredPhone)
			return ErrUserNotFound
		}
		s.logger.Error("Failed to get user by phone", "referredPhone", referredPhone, "error", err)
		return err
	}
	if !user.PhoneVerified {
		s.logger.Warn("Phone not verified for referral", "referredPhone", referredPhone)
		return ErrInvalidReferralSignup
	}

	referral.ReferredID = &referredPhone
	referral.Status = domain.ReferralCompleted
	referral.UpdatedAt = time.Now()

	if err := s.repo.UpdateReferral(ctx, referral); err != nil {
		s.logger.Error("Failed to update referral", "referralCode", referralCode, "error", err)
		return err
	}

	referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64)
	if err != nil {
		s.logger.Error("Invalid referrer phone number format", "referrerID", referral.ReferrerID, "error", err)
		return errors.New("invalid referrer phone number format")
	}

	wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID)
	if err != nil {
		s.logger.Error("Failed to get wallets for referrer", "referrerID", referrerID, "error", err)
		return err
	}
	if len(wallets) == 0 {
		s.logger.Error("Referrer has no wallet", "referrerID", referrerID)
		return errors.New("referrer has no wallet")
	}

	walletID := wallets[0].ID
	currentBonus := float64(wallets[0].Balance)
	err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(int64((currentBonus+referral.RewardAmount)*100)))
	if err != nil {
		s.logger.Error("Failed to add referral reward to wallet", "walletID", walletID, "referrerID", referrerID, "error", err)
		return err
	}

	s.logger.Info("Referral processed successfully", "referredPhone", referredPhone, "referralCode", referralCode, "rewardAmount", referral.RewardAmount)
	return nil
}

func (s *Service) ProcessDepositBonus(ctx context.Context, userPhone string, amount float64) error {
	s.logger.Info("Processing deposit bonus", "userPhone", userPhone, "amount", amount)

	settings, err := s.repo.GetSettings(ctx)
	if err != nil {
		s.logger.Error("Failed to get referral settings", "error", err)
		return err
	}

	userID, err := strconv.ParseInt(userPhone, 10, 64)
	if err != nil {
		s.logger.Error("Invalid phone number format", "userPhone", userPhone, "error", err)
		return errors.New("invalid phone number format")
	}

	wallets, err := s.walletSvc.GetWalletsByUser(ctx, userID)
	if err != nil {
		s.logger.Error("Failed to get wallets for user", "userID", userID, "error", err)
		return err
	}
	if len(wallets) == 0 {
		s.logger.Error("User has no wallet", "userID", userID)
		return errors.New("user has no wallet")
	}

	walletID := wallets[0].ID
	bonus := amount * (settings.CashbackPercentage / 100)
	currentBonus := float64(wallets[0].Balance)
	err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(int64((currentBonus+bonus)*100)))
	if err != nil {
		s.logger.Error("Failed to add deposit bonus to wallet", "walletID", walletID, "userID", userID, "bonus", bonus, "error", err)
		return err
	}

	s.logger.Info("Deposit bonus processed successfully", "userPhone", userPhone, "bonus", bonus)
	return nil
}

func (s *Service) ProcessBetReferral(ctx context.Context, userPhone string, betAmount float64) error {
	s.logger.Info("Processing bet referral", "userPhone", userPhone, "betAmount", betAmount)

	settings, err := s.repo.GetSettings(ctx)
	if err != nil {
		s.logger.Error("Failed to get referral settings", "error", err)
		return err
	}

	referral, err := s.repo.GetReferralByReferredID(ctx, userPhone)
	if err != nil {
		s.logger.Error("Failed to get referral by referred ID", "userPhone", userPhone, "error", err)
		return err
	}
	if referral == nil || referral.Status != domain.ReferralCompleted {
		s.logger.Warn("No valid referral found", "userPhone", userPhone, "status", referral.Status)
		return ErrNoReferralFound
	}

	referrerID, err := strconv.ParseInt(referral.ReferrerID, 10, 64)
	if err != nil {
		s.logger.Error("Invalid referrer phone number format", "referrerID", referral.ReferrerID, "error", err)
		return errors.New("invalid referrer phone number format")
	}

	wallets, err := s.walletSvc.GetWalletsByUser(ctx, referrerID)
	if err != nil {
		s.logger.Error("Failed to get wallets for referrer", "referrerID", referrerID, "error", err)
		return err
	}
	if len(wallets) == 0 {
		s.logger.Error("Referrer has no wallet", "referrerID", referrerID)
		return errors.New("referrer has no wallet")
	}

	bonusPercentage := settings.BetReferralBonusPercentage
	if bonusPercentage == 0 {
		bonusPercentage = 5.0
		s.logger.Debug("Using default bet referral bonus percentage", "percentage", bonusPercentage)
	}
	bonus := betAmount * (bonusPercentage / 100)

	walletID := wallets[0].ID
	currentBalance := float64(wallets[0].Balance)
	err = s.walletSvc.AddToWallet(ctx, walletID, domain.Currency(int64((currentBalance+bonus)*100)))
	if err != nil {
		s.logger.Error("Failed to add bet referral bonus to wallet", "walletID", walletID, "referrerID", referrerID, "bonus", bonus, "error", err)
		return err
	}

	s.logger.Info("Bet referral processed successfully", "userPhone", userPhone, "referrerID", referrerID, "bonus", bonus)
	return nil
}

func (s *Service) GetReferralStats(ctx context.Context, userPhone string) (*domain.ReferralStats, error) {
	s.logger.Info("Fetching referral stats", "userPhone", userPhone)

	stats, err := s.repo.GetReferralStats(ctx, userPhone)
	if err != nil {
		s.logger.Error("Failed to get referral stats", "userPhone", userPhone, "error", err)
		return nil, err
	}

	s.logger.Info("Referral stats retrieved successfully", "userPhone", userPhone, "totalReferrals", stats.TotalReferrals)
	return stats, nil
}

func (s *Service) UpdateReferralSettings(ctx context.Context, settings *domain.ReferralSettings) error {
	s.logger.Info("Updating referral settings", "settingsID", settings.ID)

	settings.UpdatedAt = time.Now()
	err := s.repo.UpdateSettings(ctx, settings)
	if err != nil {
		s.logger.Error("Failed to update referral settings", "settingsID", settings.ID, "error", err)
		return err
	}

	s.logger.Info("Referral settings updated successfully", "settingsID", settings.ID)
	return nil
}

func (s *Service) GetReferralSettings(ctx context.Context) (*domain.ReferralSettings, error) {
	s.logger.Info("Fetching referral settings")

	settings, err := s.repo.GetSettings(ctx)
	if err != nil {
		s.logger.Error("Failed to get referral settings", "error", err)
		return nil, err
	}

	s.logger.Info("Referral settings retrieved successfully", "settingsID", settings.ID)
	return settings, nil
}
