package assignments

import (
	"boostai-backend/internal/aireview"
	"boostai-backend/internal/assignmentgen"
	"boostai-backend/internal/handlers/api/shared"
	"boostai-backend/internal/sqlc"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/jackc/pgx/v5"
)

func (h *Handler) generateMixedStudentQuestionsForAssignmentStudent(
	ctx context.Context,
	assignmentID int64,
	studentID int64,
	teacherID int64,
	req *generateMixedStudentQuestionsRequest,
) (generateMixedStudentQuestionsResponse, error) {
	if h.assignmentGenerator == nil {
		return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusServiceUnavailable, code: "generator_unavailable", message: "Assignment generator is not configured"}
	}

	primaryTopic, err := parseQuestionTopicValue(req.PrimaryTopic)
	if err != nil {
		return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusBadRequest, code: "invalid_request", message: err.Error()}
	}

	primaryDifficulty, err := parseQuestionDifficultyValue(req.PrimaryDifficulty)
	if err != nil {
		return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusBadRequest, code: "invalid_request", message: err.Error()}
	}

	if req.TotalQuestions <= 0 {
		return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusBadRequest, code: "invalid_request", message: "total_questions must be greater than 0"}
	}

	personalizedDifficulty := primaryDifficulty
	if req.PersonalizedDifficulty != nil && strings.TrimSpace(*req.PersonalizedDifficulty) != "" {
		personalizedDifficulty, err = parseQuestionDifficultyValue(*req.PersonalizedDifficulty)
		if err != nil {
			return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusBadRequest, code: "invalid_request", message: err.Error()}
		}
	}

	questionStatus := sqlc.QuestionStatusDraft
	if req.QuestionStatus != nil && strings.TrimSpace(*req.QuestionStatus) != "" {
		questionStatus, err = parseQuestionStatusValue(*req.QuestionStatus)
		if err != nil {
			return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusBadRequest, code: "invalid_request", message: err.Error()}
		}
	}

	personalizedRatio := 0.0
	if req.PersonalizedRatio != nil {
		personalizedRatio = *req.PersonalizedRatio
	}

	assignment, err := h.queries.GetAssignmentByID(ctx, assignmentID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusNotFound, code: "not_found", message: "Assignment not found"}
		}
		return generateMixedStudentQuestionsResponse{}, err
	}

	if assignment.TeacherID != teacherID {
		return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusForbidden, code: "forbidden", message: "You can only generate questions for your own assignments"}
	}

	_, err = h.queries.GetAssignmentAssignee(ctx, sqlc.GetAssignmentAssigneeParams{
		AssignmentID: assignmentID,
		StudentID:    studentID,
	})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusNotFound, code: "not_found", message: "Student is not assigned to this assignment"}
		}
		return generateMixedStudentQuestionsResponse{}, err
	}

	result, err := h.assignmentGenerator.GenerateAndStoreMixedStudentQuestions(ctx, assignmentgen.GenerateMixedStudentQuestionSetParams{
		AssignmentID:           assignmentID,
		StudentID:              studentID,
		TeacherID:              teacherID,
		Subject:                trimmedPointerValue(req.Subject),
		QuestionStatus:         questionStatus,
		QuestionSource:         trimmedPointerValue(req.QuestionSource),
		PrimaryTopic:           primaryTopic,
		PrimaryDifficulty:      primaryDifficulty,
		TotalQuestions:         req.TotalQuestions,
		PersonalizedRatio:      personalizedRatio,
		Seed:                   int64Value(req.Seed),
		PersonalizedDifficulty: personalizedDifficulty,
	})
	if err != nil {
		return generateMixedStudentQuestionsResponse{}, &assignmentAPIError{status: fiber.StatusBadRequest, code: "generation_failed", message: err.Error()}
	}

	questions := make([]mixedPlanQuestionResponse, 0, len(result.StoredQuestions))
	for _, item := range result.StoredQuestions {
		questions = append(questions, mixedPlanQuestionResponse{
			MappingID:         item.Mapping.ID,
			QuestionID:        item.Question.ID,
			Position:          item.Mapping.Position,
			SourceBucket:      item.Mapping.SourceBucket,
			SourceTopic:       questionTopicPointer(item.Mapping.SourceTopic),
			SourceDifficulty:  questionDifficultyPointer(item.Mapping.SourceDifficulty),
			GeneratorSeed:     int64Pointer(item.UsedSeed),
			Title:             item.Question.Title,
			Prompt:            item.Question.Prompt,
			Subject:           shared.TextPointer(item.Question.Subject),
			QuestionStatus:    string(item.Question.Status),
			QuestionSource:    shared.TextPointer(item.Question.Source),
			CorrectAnswer:     shared.TextPointer(item.Question.CorrectAnswer),
			Tags:              item.Tags,
			QuestionCreatedAt: shared.TimePointer(item.Question.CreatedAt),
			QuestionUpdatedAt: shared.TimePointer(item.Question.UpdatedAt),
		})
	}

	response := generateMixedStudentQuestionsResponse{
		AssignmentID:        assignmentID,
		StudentID:           studentID,
		PrimaryTopic:        string(primaryTopic),
		PrimaryDifficulty:   string(primaryDifficulty),
		TotalQuestions:      req.TotalQuestions,
		CoreCount:           result.MixedPlan.CoreCount,
		PersonalizedCount:   result.MixedPlan.PersonalizedCount,
		PersonalizedApplied: result.MixedPlan.PersonalizedApplied,
		PersonalizedRatio:   personalizedRatioValue(req.PersonalizedRatio),
		BaseSeed:            result.MixedPlan.BaseSeed,
		WeaknessSummary:     mapAssignmentGenerationWeaknessSummary(result.MixedPlan.WeaknessSummary),
		Questions:           questions,
	}
	if result.MixedPlan.PersonalizedApplied {
		response.PersonalizedTopic = stringPointer(string(result.MixedPlan.PersonalizedTopic))
	}

	return response, nil
}

func (h *Handler) generateAndStoreRedoPlan(assignmentID, studentID int64, teacherFeedback string) error {
	summary, err := h.buildStudentWeaknessSummary(studentID)
	if err != nil {
		return fmt.Errorf("build weakness summary: %w", err)
	}

	stored := storedRedoPlan{
		TeacherFeedback: teacherFeedback,
		WeaknessSummary: summary,
	}

	if h.aiReview != nil && h.aiReview.Enabled() {
		assignmentCtx, assignmentCancel := shared.WithTimeout()
		assignment, err := h.queries.GetAssignmentByID(assignmentCtx, assignmentID)
		assignmentCancel()
		if err != nil {
			return fmt.Errorf("load assignment for redo plan: %w", err)
		}

		passThreshold := fixedPassThreshold
		if value := shared.NumericPointer(assignment.PassThreshold); value != nil {
			passThreshold = *value
		}

		planCtx, planCancel := context.WithTimeout(context.Background(), 45*time.Second)
		defer planCancel()

		plan, planErr := h.aiReview.PlanRedoAssignment(planCtx, aireview.RedoPlanInput{
			AssignmentID:        assignmentID,
			StudentID:           studentID,
			AssignmentTitle:     assignment.Title,
			Instructions:        strings.TrimSpace(shared.TextValue(assignment.Instructions)),
			TeacherFeedback:     teacherFeedback,
			PassThreshold:       passThreshold,
			TopicScores:         summary.TopicScores,
			WeakTags:            summary.WeakTags,
			RecentIssues:        summary.RecentIssues,
			AllowedTopics:       allowedQuestionTopics(),
			AllowedDifficulties: []string{"easy", "medium", "hard"},
		})
		if planErr != nil {
			stored.Error = fmt.Sprintf("AI redo plan could not be generated automatically: %v", planErr)
		} else {
			stored.Plan = plan
		}
	}

	payload, err := json.Marshal(stored)
	if err != nil {
		return fmt.Errorf("marshal redo plan payload: %w", err)
	}

	updateCtx, updateCancel := shared.WithTimeout()
	defer updateCancel()
	_, err = h.queries.UpdateAssignmentRedoPlan(updateCtx, sqlc.UpdateAssignmentRedoPlanParams{
		AssignmentID: assignmentID,
		StudentID:    studentID,
		Column3:      string(payload),
	})
	if err != nil {
		return fmt.Errorf("persist redo plan: %w", err)
	}

	return nil
}

func (h *Handler) buildStudentWeaknessSummary(studentID int64) (weaknessSummary, error) {
	ctx, cancel := shared.WithTimeout()
	rows, err := h.queries.ListStudentPlanningPerformance(ctx, studentID)
	cancel()
	if err != nil {
		return weaknessSummary{}, err
	}

	topicTotals := map[string]struct {
		sum   float64
		count int
	}{}
	tagTotals := map[string]struct {
		sum     float64
		count   int
		flagged int
	}{}
	recentIssues := make([]string, 0, 5)
	seenIssues := map[string]struct{}{}

	for _, row := range rows {
		score := planningScore(row.IsCorrect, row.ReviewUnderstandingScore)
		if row.Topic.Valid {
			key := string(row.Topic.QuestionTopic)
			total := topicTotals[key]
			total.sum += score
			total.count++
			topicTotals[key] = total
		}

		for _, tag := range row.QuestionTags {
			tag = strings.TrimSpace(tag)
			if tag == "" {
				continue
			}
			total := tagTotals[tag]
			total.sum += score
			total.count++
			if row.ReviewNeedsAttention {
				total.flagged++
			}
			tagTotals[tag] = total
		}

		issue := strings.TrimSpace(shared.TextValue(row.ReviewIssueReason))
		if issue != "" {
			if _, exists := seenIssues[issue]; !exists {
				seenIssues[issue] = struct{}{}
				recentIssues = append(recentIssues, issue)
				if len(recentIssues) >= 5 {
					// keep collecting scores, but no need for more issue strings
				}
			}
		}
	}

	topicScores := make(map[string]float64, len(topicTotals))
	for topic, total := range topicTotals {
		if total.count == 0 {
			continue
		}
		topicScores[topic] = roundToOneDecimal((total.sum / float64(total.count)) * 100)
	}

	type weakTagCandidate struct {
		tag     string
		score   float64
		flagged int
	}
	candidates := make([]weakTagCandidate, 0, len(tagTotals))
	for tag, total := range tagTotals {
		if total.count == 0 {
			continue
		}
		avg := (total.sum / float64(total.count)) * 100
		if avg < 70 || total.flagged > 0 {
			candidates = append(candidates, weakTagCandidate{tag: tag, score: avg, flagged: total.flagged})
		}
	}
	sort.Slice(candidates, func(i, j int) bool {
		if candidates[i].score == candidates[j].score {
			if candidates[i].flagged == candidates[j].flagged {
				return candidates[i].tag < candidates[j].tag
			}
			return candidates[i].flagged > candidates[j].flagged
		}
		return candidates[i].score < candidates[j].score
	})
	weakTags := make([]string, 0, minInt(len(candidates), 6))
	for _, candidate := range candidates {
		weakTags = append(weakTags, candidate.tag)
		if len(weakTags) >= 6 {
			break
		}
	}

	if len(recentIssues) > 5 {
		recentIssues = recentIssues[:5]
	}

	return weaknessSummary{
		TopicScores:  topicScores,
		WeakTags:     weakTags,
		RecentIssues: recentIssues,
	}, nil
}
