package service

import (
	"context"
	"errors"
	"fmt"
	"sno/internal/models"
	"sno/internal/repository"
	"time"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
	"golang.org/x/exp/slices"
)

// QuizService defines the interface for quiz-related business logic.
type QuizService interface {
	ListActiveQuizzes(ctx context.Context) ([]models.Quiz, error)
	CreateQuiz(ctx context.Context, quiz *models.Quiz) (*models.Quiz, error)
	GetQuizByID(ctx context.Context, id int) (*models.Quiz, error)
	UpdateQuiz(ctx context.Context, id int, quiz *models.Quiz) (*models.Quiz, error)
	DeleteQuiz(ctx context.Context, id int) error
	SubmitQuiz(ctx context.Context, userID int64, quizID int, submission models.SubmissionRequest) (*models.QuizAttempt, error)
	CanUserRepeatQuiz(ctx context.Context, userID int64, quizID int) (*models.CanRepeatResponse, error)
}

// quizService implements the QuizService interface.
type quizService struct {
	db              *pgxpool.Pool // For transactions
	quizRepo        repository.QuizRepository
	questionRepo    repository.QuestionRepository
	userRepo        repository.UserRepository
	userService     UserService
	quizAttemptRepo repository.QuizAttemptRepository
}

// NewQuizService creates a new instance of a quiz service.
func NewQuizService(db *pgxpool.Pool, qr repository.QuizRepository, questionr repository.QuestionRepository, ur repository.UserRepository, us UserService, qar repository.QuizAttemptRepository) QuizService {
	return &quizService{
		db:              db,
		quizRepo:        qr,
		questionRepo:    questionr,
		userRepo:        ur,
		userService:     us,
		quizAttemptRepo: qar,
	}
}

// GetQuizByID retrieves a quiz and all its associated questions.
func (s *quizService) GetQuizByID(ctx context.Context, id int) (*models.Quiz, error) {
	quiz, err := s.quizRepo.GetByID(ctx, id)
	if err != nil {
		return nil, err
	}
	questions, err := s.questionRepo.GetByQuizID(ctx, id)
	if err != nil {
		return nil, err
	}
	quiz.Questions = questions
	return quiz, nil
}

// SubmitQuiz validates answers, calculates score, and awards stars.
func (s *quizService) SubmitQuiz(ctx context.Context, userID int64, quizID int, submission models.SubmissionRequest) (*models.QuizAttempt, error) {
	quiz, err := s.GetQuizByID(ctx, quizID)
	if err != nil {
		return nil, fmt.Errorf("failed to get quiz for submission: %w", err)
	}

	// Ensure user exists before proceeding
	_, err = s.userService.GetUserProfile(ctx, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to get user profile: %w", err)
	}

	if len(quiz.Questions) == 0 {
		return nil, errors.New("cannot submit to a quiz with no questions")
	}

	// --- Submission Cooldown/Uniqueness Validation ---
	lastAttempt, err := s.quizAttemptRepo.GetLatestByUserIDAndQuizID(ctx, userID, quizID)
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return nil, fmt.Errorf("failed to check previous attempts: %w", err)
	}

	if lastAttempt != nil {
		if !quiz.CanRepeat {
			return nil, errors.New("quiz has already been completed")
		}
		if quiz.RepeatCooldownHours != nil {
			cooldown := time.Duration(*quiz.RepeatCooldownHours) * time.Hour
			if time.Since(lastAttempt.CompletedAt) < cooldown {
				return nil, fmt.Errorf("quiz is on cooldown, please try again later")
			}
		}
	}

	// --- Scoring Logic ---
	score := 0
	correctAnswers := make(map[int][]int) // questionID -> sorted list of correct optionIDs
	for _, q := range quiz.Questions {
		for _, o := range q.Options {
			if o.IsCorrect {
				correctAnswers[q.ID] = append(correctAnswers[q.ID], o.ID)
			}
		}
		slices.Sort(correctAnswers[q.ID])
	}

	for _, userAnswer := range submission.Answers {
		correct, exists := correctAnswers[userAnswer.QuestionID]
		if !exists {
			continue // User answered a question not in the quiz, ignore it.
		}
		slices.Sort(userAnswer.OptionIDs)
		if slices.Equal(correct, userAnswer.OptionIDs) {
			score++
		}
	}

	starsEarned := 0
	if len(quiz.Questions) > 0 {
		starsEarned = int(float64(score) / float64(len(quiz.Questions)) * float64(quiz.RewardStars))
	}

	// --- Database Transaction ---
	attempt := &models.QuizAttempt{
		UserID:      userID,
		QuizID:      quizID,
		Score:       score,
		StarsEarned: starsEarned,
		Answers:     submission.Answers,
	}

	tx, err := s.db.Begin(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer tx.Rollback(ctx) // Rollback is a no-op if tx is committed

	// Create attempt record
	if _, err := s.quizAttemptRepo.Create(context.WithValue(ctx, "tx", tx), attempt); err != nil {
		return nil, fmt.Errorf("failed to create quiz attempt: %w", err)
	}

	// Update user balance
	if starsEarned > 0 {
		if err := s.userRepo.UpdateStarsBalance(context.WithValue(ctx, "tx", tx), userID, starsEarned); err != nil {
			return nil, fmt.Errorf("failed to update user balance: %w", err)
		}
	}

	if err := tx.Commit(ctx); err != nil {
		return nil, fmt.Errorf("failed to commit transaction: %w", err)
	}

	return attempt, nil
}

// ListActiveQuizzes and CreateQuiz remain the same for now
func (s *quizService) ListActiveQuizzes(ctx context.Context) ([]models.Quiz, error) {
	return s.quizRepo.GetAllActive(ctx)
}
func (s *quizService) CreateQuiz(ctx context.Context, quiz *models.Quiz) (*models.Quiz, error) {
	return s.quizRepo.Create(ctx, quiz)
}

// CanUserRepeatQuiz checks if a user is allowed to repeat a specific quiz.
func (s *quizService) CanUserRepeatQuiz(ctx context.Context, userID int64, quizID int) (*models.CanRepeatResponse, error) {
	quiz, err := s.quizRepo.GetByID(ctx, quizID)
	if err != nil {
		return nil, fmt.Errorf("failed to get quiz: %w", err)
	}

	lastAttempt, err := s.quizAttemptRepo.GetLatestByUserIDAndQuizID(ctx, userID, quizID)
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return nil, fmt.Errorf("failed to check previous attempts: %w", err)
	}

	// No previous attempt, so user can take the quiz
	if lastAttempt == nil {
		return &models.CanRepeatResponse{CanRepeat: true}, nil
	}

	// Quiz is marked as non-repeatable
	if !quiz.CanRepeat {
		return &models.CanRepeatResponse{CanRepeat: false}, nil
	}

	// Quiz is repeatable, check cooldown
	if quiz.RepeatCooldownHours != nil {
		cooldown := time.Duration(*quiz.RepeatCooldownHours) * time.Hour
		nextAvailableAt := lastAttempt.CompletedAt.Add(cooldown)
		if time.Now().Before(nextAvailableAt) {
			return &models.CanRepeatResponse{
				CanRepeat:       false,
				NextAvailableAt: &nextAvailableAt,
			}, nil
		}
	}

	// Cooldown has passed or doesn't exist
	return &models.CanRepeatResponse{CanRepeat: true}, nil
}

// UpdateQuiz updates an existing quiz
func (s *quizService) UpdateQuiz(ctx context.Context, id int, quiz *models.Quiz) (*models.Quiz, error) {
	// Check if quiz exists
	existingQuiz, err := s.quizRepo.GetByID(ctx, id)
	if err != nil {
		return nil, fmt.Errorf("failed to get existing quiz: %w", err)
	}
	if existingQuiz == nil {
		return nil, errors.New("quiz not found")
	}

	// Update quiz
	updatedQuiz, err := s.quizRepo.Update(ctx, id, quiz)
	if err != nil {
		return nil, fmt.Errorf("failed to update quiz: %w", err)
	}

	return updatedQuiz, nil
}

// DeleteQuiz deletes a quiz
func (s *quizService) DeleteQuiz(ctx context.Context, id int) error {
	// Check if quiz exists
	existingQuiz, err := s.quizRepo.GetByID(ctx, id)
	if err != nil {
		return fmt.Errorf("failed to get existing quiz: %w", err)
	}
	if existingQuiz == nil {
		return errors.New("quiz not found")
	}

	// Delete quiz and associated questions
	err = s.quizRepo.Delete(ctx, id)
	if err != nil {
		return fmt.Errorf("failed to delete quiz: %w", err)
	}

	return nil
}
