package repository

import (
	"apskel-pos-be/internal/entities"
	"context"
	"math/rand"
	"time"

	"gorm.io/gorm"
)

type VoucherRepository struct {
	db *gorm.DB
}

func NewVoucherRepository(db *gorm.DB) *VoucherRepository {
	return &VoucherRepository{db: db}
}

func (r *VoucherRepository) Create(ctx context.Context, voucher *entities.Voucher) error {
	return r.db.WithContext(ctx).Create(voucher).Error
}

func (r *VoucherRepository) GetByID(ctx context.Context, id int64) (*entities.Voucher, error) {
	var voucher entities.Voucher
	err := r.db.WithContext(ctx).Where("id = ?", id).First(&voucher).Error
	if err != nil {
		return nil, err
	}
	return &voucher, nil
}

func (r *VoucherRepository) GetByVoucherCode(ctx context.Context, voucherCode string) (*entities.Voucher, error) {
	var voucher entities.Voucher
	err := r.db.WithContext(ctx).Where("voucher_code = ?", voucherCode).First(&voucher).Error
	if err != nil {
		return nil, err
	}
	return &voucher, nil
}

func (r *VoucherRepository) List(ctx context.Context, offset, limit int) ([]entities.Voucher, int64, error) {
	var vouchers []entities.Voucher
	var total int64

	query := r.db.WithContext(ctx)

	if err := query.Model(&entities.Voucher{}).Count(&total).Error; err != nil {
		return nil, 0, err
	}

	err := query.Offset(offset).Limit(limit).Find(&vouchers).Error
	if err != nil {
		return nil, 0, err
	}

	return vouchers, total, nil
}

func (r *VoucherRepository) GetRandomVouchers(ctx context.Context, limit int) ([]entities.Voucher, error) {
	var vouchers []entities.Voucher

	// First, get the total count
	var total int64
	if err := r.db.WithContext(ctx).Model(&entities.Voucher{}).Count(&total).Error; err != nil {
		return nil, err
	}

	if total == 0 {
		return vouchers, nil
	}

	// If we have fewer vouchers than requested, return all
	if int(total) <= limit {
		err := r.db.WithContext(ctx).Find(&vouchers).Error
		return vouchers, err
	}

	// Generate random offsets to get random vouchers
	rand.Seed(time.Now().UnixNano())
	usedOffsets := make(map[int]bool)

	for len(vouchers) < limit && len(usedOffsets) < int(total) {
		offset := rand.Intn(int(total))
		if usedOffsets[offset] {
			continue
		}
		usedOffsets[offset] = true

		var voucher entities.Voucher
		err := r.db.WithContext(ctx).Offset(offset).Limit(1).First(&voucher).Error
		if err != nil {
			continue
		}
		vouchers = append(vouchers, voucher)
	}

	return vouchers, nil
}

func (r *VoucherRepository) GetRandomVouchersByRows(ctx context.Context, rows int, winnerNumber *int) ([][]entities.Voucher, error) {
	var allVouchers []entities.Voucher
	var err error

	// First, try to get vouchers based on winner_number parameter
	if winnerNumber != nil {
		// If winner_number is provided, filter by it and exclude already won vouchers
		query := r.db.WithContext(ctx).Where("winner_number = ? AND is_winner = ?", *winnerNumber, false)
		err = query.Find(&allVouchers).Error
		if err != nil {
			return nil, err
		}

		// If no vouchers found for the specified winner_number, fallback to winner_number = 0
		if len(allVouchers) == 0 {
			fallbackQuery := r.db.WithContext(ctx).Where("winner_number = ? AND is_winner = ?", 0, false)
			err = fallbackQuery.Find(&allVouchers).Error
			if err != nil {
				return nil, err
			}
		}

		// If still no vouchers found, try without is_winner filter for winner_number = 0
		if len(allVouchers) == 0 {
			fallbackQuery2 := r.db.WithContext(ctx).Where("winner_number = ?", 0)
			err = fallbackQuery2.Find(&allVouchers).Error
			if err != nil {
				return nil, err
			}
		}

		// If still no vouchers found, get any vouchers available
		if len(allVouchers) == 0 {
			err = r.db.WithContext(ctx).Find(&allVouchers).Error
			if err != nil {
				return nil, err
			}
		}
	} else {
		// If winner_number is not provided, use default winner_number = 0 and exclude already won vouchers
		query := r.db.WithContext(ctx).Where("winner_number = ? AND is_winner = ?", 0, false)
		err = query.Find(&allVouchers).Error
		if err != nil {
			return nil, err
		}

		// If no vouchers found, try without is_winner filter for winner_number = 0
		if len(allVouchers) == 0 {
			fallbackQuery := r.db.WithContext(ctx).Where("winner_number = ?", 0)
			err = fallbackQuery.Find(&allVouchers).Error
			if err != nil {
				return nil, err
			}
		}

		// If still no vouchers found, get any vouchers available
		if len(allVouchers) == 0 {
			err = r.db.WithContext(ctx).Find(&allVouchers).Error
			if err != nil {
				return nil, err
			}
		}
	}

	if len(allVouchers) == 0 {
		// Return empty rows if no vouchers available
		return make([][]entities.Voucher, rows), nil
	}

	// Shuffle the vouchers for randomness
	rand.Seed(time.Now().UnixNano())
	rand.Shuffle(len(allVouchers), func(i, j int) {
		allVouchers[i], allVouchers[j] = allVouchers[j], allVouchers[i]
	})

	// Calculate vouchers per row (distribute evenly)
	vouchersPerRow := len(allVouchers) / rows
	if vouchersPerRow == 0 {
		vouchersPerRow = 1 // At least 1 voucher per row
	}

	// Organize vouchers into rows
	voucherRows := make([][]entities.Voucher, rows)
	voucherIndex := 0

	for i := 0; i < rows; i++ {
		// Calculate how many vouchers this row should have
		remainingRows := rows - i
		remainingVouchers := len(allVouchers) - voucherIndex

		// Distribute remaining vouchers evenly among remaining rows
		rowSize := remainingVouchers / remainingRows
		if remainingVouchers%remainingRows > 0 {
			rowSize++
		}

		// Ensure we don't exceed available vouchers
		if voucherIndex+rowSize > len(allVouchers) {
			rowSize = len(allVouchers) - voucherIndex
		}

		// Create the row
		if voucherIndex < len(allVouchers) {
			endIdx := voucherIndex + rowSize
			if endIdx > len(allVouchers) {
				endIdx = len(allVouchers)
			}
			voucherRows[i] = allVouchers[voucherIndex:endIdx]
			voucherIndex = endIdx
		} else {
			voucherRows[i] = []entities.Voucher{} // Empty row
		}
	}

	return voucherRows, nil
}

func (r *VoucherRepository) Update(ctx context.Context, voucher *entities.Voucher) error {
	return r.db.WithContext(ctx).Save(voucher).Error
}

func (r *VoucherRepository) Delete(ctx context.Context, id int64) error {
	return r.db.WithContext(ctx).Delete(&entities.Voucher{}, id).Error
}

func (r *VoucherRepository) MarkAsWinner(ctx context.Context, id int64) error {
	return r.db.WithContext(ctx).Model(&entities.Voucher{}).Where("id = ?", id).Update("is_winner", true).Error
}
