package service

import (
	"context"
	"crypto/rand"
	"encoding/json"
	"errors"
	"fmt"
	"sno/internal/models"
	"sno/internal/repository"
	"strconv"
	"time"

	"github.com/redis/go-redis/v9"
)

// QRService defines the interface for QR code processing logic.
type QRService interface {
	ValidatePayload(ctx context.Context, userID int64, payload string) (*models.QRValidateResponse, error)
	GenerateUniqueToken(ctx context.Context, qrType string, value string) (string, error)
	ValidateUniqueToken(ctx context.Context, token string) (*models.QRTokenData, error)
}

// qrService implements the QRService interface.
type qrService struct {
	qrScanRepo repository.QRScanRepository
	adminSvc   AdminService
	quizSvc    QuizService
	redis      *redis.Client
}

// NewQRService creates a new instance of a QR service.
func NewQRService(qrRepo repository.QRScanRepository, adminSvc AdminService, quizSvc QuizService, redisClient *redis.Client) QRService {
	return &qrService{
		qrScanRepo: qrRepo,
		adminSvc:   adminSvc,
		quizSvc:    quizSvc,
		redis:      redisClient,
	}
}

// generateRandomToken generates a secure random token
func (s *qrService) generateRandomToken() (string, error) {
	b := make([]byte, 16) // 128 bits
	_, err := rand.Read(b)
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%x", b), nil
}

// GenerateUniqueToken creates a new unique QR token and stores it in Redis
func (s *qrService) GenerateUniqueToken(ctx context.Context, qrType string, value string) (string, error) {
	token, err := s.generateRandomToken()
	if err != nil {
		return "", fmt.Errorf("failed to generate token: %w", err)
	}

	tokenData := &models.QRTokenData{
		Type:  qrType,
		Value: value,
		Used:  false,
	}

	// Store in Redis with 30 day expiration
	data, err := json.Marshal(tokenData)
	if err != nil {
		return "", fmt.Errorf("failed to marshal token data: %w", err)
	}

	key := fmt.Sprintf("qr_token:%s", token)
	err = s.redis.Set(ctx, key, data, 30*24*time.Hour).Err()
	if err != nil {
		return "", fmt.Errorf("failed to store token in Redis: %w", err)
	}

	return token, nil
}

// ValidateUniqueToken validates a QR token and marks it as used
func (s *qrService) ValidateUniqueToken(ctx context.Context, token string) (*models.QRTokenData, error) {
	key := fmt.Sprintf("qr_token:%s", token)

	// Get token data from Redis
	data, err := s.redis.Get(ctx, key).Bytes()
	if err != nil {
		if err == redis.Nil {
			return nil, errors.New("invalid or expired token")
		}
		return nil, fmt.Errorf("failed to get token from Redis: %w", err)
	}

	var tokenData models.QRTokenData
	if err := json.Unmarshal(data, &tokenData); err != nil {
		return nil, fmt.Errorf("failed to unmarshal token data: %w", err)
	}

	// Check if token is already used
	if tokenData.Used {
		return nil, errors.New("token has already been used")
	}

	// Mark token as used
	tokenData.Used = true
	updatedData, err := json.Marshal(tokenData)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal updated token data: %w", err)
	}

	err = s.redis.Set(ctx, key, updatedData, 30*24*time.Hour).Err()
	if err != nil {
		return nil, fmt.Errorf("failed to update token in Redis: %w", err)
	}

	return &tokenData, nil
}

// ValidatePayload validates a QR token and performs the corresponding action.
func (s *qrService) ValidatePayload(ctx context.Context, userID int64, payload string) (*models.QRValidateResponse, error) {
	// Validate as unique token
	tokenData, err := s.ValidateUniqueToken(ctx, payload)
	if err != nil {
		return nil, err
	}

	// Process the validated token data
	return s.processTokenData(ctx, userID, tokenData, payload)
}

// processTokenData processes validated token data and returns response
func (s *qrService) processTokenData(ctx context.Context, userID int64, tokenData *models.QRTokenData, payload string) (*models.QRValidateResponse, error) {
	scanLog := &models.QRScan{
		UserID: userID,
		Source: models.InApp,
		Value:  &payload,
	}

	switch tokenData.Type {
	case "reward":
		scanLog.Type = models.QRReward
		amount, err := strconv.Atoi(tokenData.Value)
		if err != nil {
			return nil, fmt.Errorf("invalid reward amount: %s", tokenData.Value)
		}

		if err := s.adminSvc.GrantStars(ctx, userID, amount); err != nil {
			return nil, fmt.Errorf("failed to grant reward: %w", err)
		}

		// Log the successful scan
		_, _ = s.qrScanRepo.Create(ctx, scanLog)

		return &models.QRValidateResponse{
			Type: "REWARD",
			Data: map[string]int{"amount": amount},
		}, nil

	case "quiz":
		scanLog.Type = models.QRQuiz
		quizID, err := strconv.Atoi(tokenData.Value)
		if err != nil {
			return nil, fmt.Errorf("invalid quiz ID: %s", tokenData.Value)
		}

		quiz, err := s.quizSvc.GetQuizByID(ctx, quizID)
		if err != nil {
			return nil, fmt.Errorf("failed to get quiz: %w", err)
		}

		// Log the successful scan
		_, _ = s.qrScanRepo.Create(ctx, scanLog)

		return &models.QRValidateResponse{
			Type: "OPEN_QUIZ",
			Data: quiz,
		}, nil

	case "shop":
		scanLog.Type = models.QRShop
		// Shop QR codes can be used for various shop-related actions
		// For now, just log the scan and return success
		_, _ = s.qrScanRepo.Create(ctx, scanLog)

		return &models.QRValidateResponse{
			Type: "SHOP_ACTION",
			Data: map[string]string{"action": tokenData.Value},
		}, nil

	default:
		return nil, fmt.Errorf("unknown token type: %s", tokenData.Type)
	}
}
