package assignmentgen

import (
	"context"
	"fmt"
	"math"
	"sort"
	"time"

	"boostai-backend/internal/sqlc"
)

func (s *Service) BuildMixedPlan(ctx context.Context, params MixedPlanParams) (MixedPlan, error) {
	if err := validateMixedPlanParams(params); err != nil {
		return MixedPlan{}, err
	}

	ratio := normalizePersonalizedRatio(params.PersonalizedRatio)

	weaknessSummary, err := s.BuildWeaknessSummary(ctx, params.StudentID)
	if err != nil {
		return MixedPlan{}, err
	}

	personalizedTopic, personalizedApplied := selectPersonalizedTopic(params.PrimaryTopic, weaknessSummary)
	personalizedCount := calculatePersonalizedCount(params.TotalQuestions, ratio, personalizedApplied)
	coreCount := calculateCoreCount(params.TotalQuestions, personalizedCount)
	baseSeed := normalizeBaseSeed(params.BaseSeed)
	personalizedDifficulty := normalizePersonalizedDifficulty(params)

	return MixedPlan{
		Plan:                buildMixedPlanItems(params, coreCount, personalizedCount, personalizedTopic, personalizedDifficulty, baseSeed),
		WeaknessSummary:     weaknessSummary,
		CoreCount:           coreCount,
		PersonalizedCount:   personalizedCount,
		PersonalizedTopic:   personalizedTopic,
		PersonalizedApplied: personalizedCount > 0,
		BaseSeed:            baseSeed,
	}, nil
}

func validateMixedPlanParams(params MixedPlanParams) error {
	if params.StudentID <= 0 {
		return fmt.Errorf("student_id is required")
	}
	if params.PrimaryTopic == "" {
		return fmt.Errorf("primary topic is required")
	}
	if params.PrimaryDifficulty == "" {
		return fmt.Errorf("primary difficulty is required")
	}
	if params.TotalQuestions <= 0 {
		return fmt.Errorf("total_questions must be positive")
	}
	if params.PersonalizedRatio < 0 || params.PersonalizedRatio >= 1 {
		return fmt.Errorf("personalized_ratio must be between 0 and less than 1")
	}

	return nil
}

func normalizePersonalizedRatio(ratio float64) float64 {
	if ratio == 0 {
		return defaultPersonalizedRatio
	}
	return ratio
}

func normalizeBaseSeed(seed int64) int64 {
	if seed == 0 {
		return time.Now().UnixNano()
	}
	return seed
}

func normalizePersonalizedDifficulty(params MixedPlanParams) sqlc.QuestionDifficulty {
	if params.PersonalizedDifficulty == "" {
		return params.PrimaryDifficulty
	}
	return params.PersonalizedDifficulty
}

func calculateCoreCount(totalQuestions, personalizedCount int) int {
	coreCount := totalQuestions - personalizedCount
	if totalQuestions > 0 && coreCount <= 0 {
		return 1
	}
	return coreCount
}

func buildMixedPlanItems(
	params MixedPlanParams,
	coreCount int,
	personalizedCount int,
	personalizedTopic sqlc.QuestionTopic,
	personalizedDifficulty sqlc.QuestionDifficulty,
	baseSeed int64,
) []PlanItem {
	plan := make([]PlanItem, 0, 2)

	if coreCount > 0 {
		plan = append(plan, PlanItem{
			Topic:        params.PrimaryTopic,
			Difficulty:   params.PrimaryDifficulty,
			Count:        coreCount,
			SourceBucket: SourceBucketCoreTopic,
			Seed:         baseSeed,
		})
	}

	if personalizedCount > 0 {
		plan = append(plan, PlanItem{
			Topic:        personalizedTopic,
			Difficulty:   personalizedDifficulty,
			Count:        personalizedCount,
			SourceBucket: SourceBucketPersonalized,
			Seed:         baseSeed + 7919,
		})
	}

	return plan
}

func selectPersonalizedTopic(primaryTopic sqlc.QuestionTopic, summary WeaknessSummary) (sqlc.QuestionTopic, bool) {
	if len(summary.TopicScores) == 0 {
		return "", false
	}

	type scoredTopic struct {
		topic sqlc.QuestionTopic
		score float64
	}

	topics := make([]scoredTopic, 0, len(summary.TopicScores))
	for topic, score := range summary.TopicScores {
		topics = append(topics, scoredTopic{topic: topic, score: score})
	}

	sort.SliceStable(topics, func(i, j int) bool {
		if topics[i].score == topics[j].score {
			return string(topics[i].topic) < string(topics[j].topic)
		}
		return topics[i].score < topics[j].score
	})

	for _, candidate := range topics {
		if candidate.topic != primaryTopic {
			return candidate.topic, true
		}
	}

	return topics[0].topic, true
}

func calculatePersonalizedCount(totalQuestions int, ratio float64, personalizedApplied bool) int {
	if !personalizedApplied || totalQuestions <= 1 || ratio <= 0 {
		return 0
	}

	count := int(math.Floor(float64(totalQuestions) * ratio))
	if count == 0 && totalQuestions >= 3 {
		count = 1
	}
	if count >= totalQuestions {
		count = totalQuestions - 1
	}
	if count < 0 {
		count = 0
	}

	return count
}
