package service

import (
	"context"
	"errors"
	"fmt"
	"sno/internal/models"
	"sno/internal/repository"
	"sno/internal/types"
	"github.com/google/uuid"
	"github.com/redis/go-redis/v9"
)

// AdminService defines the interface for admin-related business logic.
type AdminService interface {
	GrantStars(ctx context.Context, userID int64, amount int) error
	GenerateUniqueQRCodes(ctx context.Context, qrType, qrValue string, count int) ([]string, error)
	GetUserRole(ctx context.Context, userID int64) (types.UserRole, error)
	CreateOperator(ctx context.Context, telegramID int64, name string) error
	DeleteOperator(ctx context.Context, telegramID int64) error
	GetAnalytics(ctx context.Context) (*models.Analytics, error)
}

// adminService implements the AdminService interface.
type adminService struct {
	userRepo  repository.UserRepository
	adminRepo repository.AdminRepository
	redisClient *redis.Client
}

// NewAdminService creates a new instance of an admin service.
func NewAdminService(userRepo repository.UserRepository, adminRepo repository.AdminRepository, redisClient *redis.Client) AdminService {
	return &adminService{
		userRepo:  userRepo,
		adminRepo: adminRepo,
		redisClient: redisClient,
	}
}

// GrantStars adds or removes stars from a user's balance.
func (s *adminService) GrantStars(ctx context.Context, userID int64, amount int) error {
	if amount == 0 {
		return errors.New("amount cannot be zero")
	}
	// We might want to check if the user exists first, but the UPDATE query
	// will just do nothing if the user doesn't exist, which is safe.
	return s.userRepo.UpdateStarsBalance(ctx, userID, amount)
}

// GenerateUniqueQRCodes generates unique QR codes and stores them in Redis.
// The format in Redis will be a SET named `unique_qrs:<type>:<value>`
// Each member of the set will be a UUID.
func (s *adminService) GenerateUniqueQRCodes(ctx context.Context, qrType, qrValue string, count int) ([]string, error) {
	if count <= 0 {
		return nil, errors.New("count must be positive")
	}

	key := fmt.Sprintf("unique_qrs:%s:%s", qrType, qrValue)
	codes := make([]string, count)
	members := make([]interface{}, count)

	for i := 0; i < count; i++ {
		newUUID := uuid.New().String()
		codes[i] = newUUID
		members[i] = newUUID
	}

	// Add all generated UUIDs to the Redis set
	if err := s.redisClient.SAdd(ctx, key, members...).Err(); err != nil {
		return nil, fmt.Errorf("failed to add unique QR codes to Redis: %w", err)
	}

	return codes, nil
}

// GetUserRole retrieves the user's role from the admins table
func (s *adminService) GetUserRole(ctx context.Context, userID int64) (types.UserRole, error) {
	return s.adminRepo.GetUserRole(ctx, userID)
}

// CreateOperator creates a new operator with the given telegram ID and name
func (s *adminService) CreateOperator(ctx context.Context, telegramID int64, name string) error {
	// Check if user already exists as admin
	existingAdmin, err := s.adminRepo.GetByTelegramID(ctx, telegramID)
	if err != nil {
		return fmt.Errorf("failed to check existing admin: %w", err)
	}
	if existingAdmin != nil {
		return errors.New("user is already an admin or operator")
	}

	// Create new operator
	admin := &models.Admin{
		TelegramID: telegramID,
		Role:       models.RoleOperator,
		Name:       &name,
	}

	return s.adminRepo.Create(ctx, admin)
}

// DeleteOperator deletes an operator by their telegram ID
func (s *adminService) DeleteOperator(ctx context.Context, telegramID int64) error {
	// Check if user exists and is an operator
	existingAdmin, err := s.adminRepo.GetByTelegramID(ctx, telegramID)
	if err != nil {
		return fmt.Errorf("failed to check existing admin: %w", err)
	}
	if existingAdmin == nil {
		return errors.New("operator not found")
	}
	if existingAdmin.Role != models.RoleOperator {
		return errors.New("user is not an operator")
	}

	// Delete the operator
	return s.adminRepo.Delete(ctx, telegramID)
}

// GetAnalytics retrieves various statistics and metrics for the admin dashboard
func (s *adminService) GetAnalytics(ctx context.Context) (*models.Analytics, error) {
	analytics := &models.Analytics{}

	// Query all analytics data in a single transaction for consistency
	tx, err := s.userRepo.GetDB().Begin(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(ctx)

	// Total users
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM users").Scan(&analytics.TotalUsers)
	if err != nil {
		return nil, fmt.Errorf("failed to get total users: %w", err)
	}

	// Total quizzes
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM quizzes").Scan(&analytics.TotalQuizzes)
	if err != nil {
		return nil, fmt.Errorf("failed to get total quizzes: %w", err)
	}

	// Active quizzes
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM quizzes WHERE is_active = true").Scan(&analytics.ActiveQuizzes)
	if err != nil {
		return nil, fmt.Errorf("failed to get active quizzes: %w", err)
	}

	// Total rewards
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM rewards").Scan(&analytics.TotalRewards)
	if err != nil {
		return nil, fmt.Errorf("failed to get total rewards: %w", err)
	}

	// Active rewards
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM rewards WHERE is_active = true").Scan(&analytics.ActiveRewards)
	if err != nil {
		return nil, fmt.Errorf("failed to get active rewards: %w", err)
	}

	// Total quiz attempts
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM quiz_attempts").Scan(&analytics.TotalAttempts)
	if err != nil {
		return nil, fmt.Errorf("failed to get total quiz attempts: %w", err)
	}

	// Total purchases
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM purchases").Scan(&analytics.TotalPurchases)
	if err != nil {
		return nil, fmt.Errorf("failed to get total purchases: %w", err)
	}

	// Total QR scans
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM qr_scans").Scan(&analytics.TotalQRScans)
	if err != nil {
		return nil, fmt.Errorf("failed to get total QR scans: %w", err)
	}

	// Stars distributed (from quiz attempts)
	err = tx.QueryRow(ctx, "SELECT COALESCE(SUM(stars_earned), 0) FROM quiz_attempts").Scan(&analytics.StarsDistributed)
	if err != nil {
		return nil, fmt.Errorf("failed to get stars distributed: %w", err)
	}

	// Stars spent (from purchases)
	err = tx.QueryRow(ctx, "SELECT COALESCE(SUM(stars_spent), 0) FROM purchases").Scan(&analytics.StarsSpent)
	if err != nil {
		return nil, fmt.Errorf("failed to get stars spent: %w", err)
	}

	return analytics, nil
}
