// Path: Backend/internal/handlers/api/answers/handler.go

package answers

import (
	"boostai-backend/internal/aireview"
	"boostai-backend/internal/handlers/api/shared"
	"boostai-backend/internal/http/params"
	"boostai-backend/internal/http/respond"
	authmw "boostai-backend/internal/middleware"
	"boostai-backend/internal/sqlc"
	"context"
	"errors"
	"fmt"
	"log"
	"strings"
	"time"

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

type Handler struct {
	queries   *sqlc.Queries
	aiReview *aireview.Service
}

type StudentAnswerResponse struct {
	ID                       int64      `json:"id"`
	AssignmentID             int64      `json:"assignment_id"`
	QuestionID               int64      `json:"question_id"`
	StudentID                int64      `json:"student_id"`
	AnswerText               *string    `json:"answer_text,omitempty"`
	IsCorrect                *bool      `json:"is_correct,omitempty"`
	SolveMode                string     `json:"solve_mode"`
	WorkingSteps             *string    `json:"working_steps,omitempty"`
	AiFeedback               *string    `json:"ai_feedback,omitempty"`
	TeacherFeedback          *string    `json:"teacher_feedback,omitempty"`
	Status                   string     `json:"status"`
	ReviewNeedsAttention     bool       `json:"review_needs_attention"`
	ReviewIssueReason        *string    `json:"review_issue_reason,omitempty"`
	ReviewCorrectnessScore   *float64   `json:"review_correctness_score,omitempty"`
	ReviewUnderstandingScore *float64   `json:"review_understanding_score,omitempty"`
	ReviewQuestionScore      *float64   `json:"review_question_score,omitempty"`
	ReviewConfidence         *float64   `json:"review_confidence,omitempty"`
	ReviewTags               []string   `json:"review_tags"`
	SubmittedAt              *time.Time `json:"submitted_at,omitempty"`
	ReviewedAt               *time.Time `json:"reviewed_at,omitempty"`
	CreatedAt                *time.Time `json:"created_at,omitempty"`
	UpdatedAt                *time.Time `json:"updated_at,omitempty"`
}

type upsertStudentAnswerRequest struct {
	AssignmentID    int64      `json:"assignment_id"`
	QuestionID      int64      `json:"question_id"`
	StudentID       int64      `json:"student_id"`
	AnswerText      *string    `json:"answer_text"`
	SolveMode       string     `json:"solve_mode"`
	WorkingSteps    *string    `json:"working_steps"`
	AiFeedback      *string    `json:"ai_feedback"`
	TeacherFeedback *string    `json:"teacher_feedback"`
	Status          string     `json:"status"`
	SubmittedAt     *time.Time `json:"submitted_at"`
	ReviewedAt      *time.Time `json:"reviewed_at"`
}

type updateAnswerReviewRequest struct {
	Status                   string   `json:"status"`
	ReviewNeedsAttention     *bool    `json:"review_needs_attention"`
	ReviewIssueReason        *string  `json:"review_issue_reason"`
	ReviewCorrectnessScore   *float64 `json:"review_correctness_score"`
	ReviewUnderstandingScore *float64 `json:"review_understanding_score"`
	ReviewQuestionScore      *float64 `json:"review_question_score"`
	ReviewConfidence         *float64 `json:"review_confidence"`
	ReviewTags               []string `json:"review_tags"`
}

func NewHandler(queries *sqlc.Queries, aiReview *aireview.Service) *Handler {
	return &Handler{queries: queries, aiReview: aiReview}
}

func (h *Handler) ListAnswersForAssignment(c *fiber.Ctx) error {
	assignmentID, err := params.Int64PathParam(c, "assignmentId")
	if err != nil {
		return err
	}

	ctx, cancel := shared.WithTimeout()
	defer cancel()

	answers, err := h.queries.ListAnswersForAssignment(ctx, assignmentID)
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	items := make([]StudentAnswerResponse, 0, len(answers))
	for _, answer := range answers {
		items = append(items, mapStudentAnswer(answer))
	}

	return c.JSON(shared.ListResponse[StudentAnswerResponse]{Data: items})
}

func (h *Handler) ListAnswersForStudent(c *fiber.Ctx) error {
	studentID, err := params.Int64PathParam(c, "studentId")
	if err != nil {
		return err
	}

	ctx, cancel := shared.WithTimeout()
	defer cancel()

	answers, err := h.queries.ListAnswersForStudent(ctx, studentID)
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	items := make([]StudentAnswerResponse, 0, len(answers))
	for _, answer := range answers {
		items = append(items, mapStudentAnswer(answer))
	}

	return c.JSON(shared.ListResponse[StudentAnswerResponse]{Data: items})
}

func (h *Handler) UpsertStudentAnswer(c *fiber.Ctx) error {
	var req upsertStudentAnswerRequest
	if err := c.BodyParser(&req); err != nil {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "Unable to parse request body")
	}

	studentID := req.StudentID
	if authmw.CurrentUserRole(c) == sqlc.UserRoleStudent {
		studentID = authmw.CurrentUserID(c)
	}

	if req.AssignmentID == 0 || req.QuestionID == 0 || studentID == 0 || strings.TrimSpace(req.Status) == "" {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "assignment_id, question_id, student identity, and status are required")
	}

	solveMode := strings.TrimSpace(req.SolveMode)
	if solveMode == "" {
		solveMode = "just_answer"
	}

	if !isValidSolveMode(solveMode) {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "A valid solve_mode is required")
	}

	ctx, cancel := shared.WithTimeout()
	defer cancel()

	question, err := h.queries.GetQuestionByID(ctx, req.QuestionID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return respond.Error(c, fiber.StatusNotFound, "not_found", "Question not found")
		}
		return respond.DatabaseError(c, err)
	}

	isCorrect := compareAnswer(question.CorrectAnswer, req.AnswerText)

	answer, err := h.queries.UpsertStudentAnswer(ctx, sqlc.UpsertStudentAnswerParams{
		AssignmentID:    req.AssignmentID,
		QuestionID:      req.QuestionID,
		StudentID:       studentID,
		AnswerText:      shared.NullableText(req.AnswerText),
		SolveMode:       solveMode,
		WorkingSteps:    shared.NullableText(req.WorkingSteps),
		AiFeedback:      shared.NullableText(req.AiFeedback),
		TeacherFeedback: shared.NullableText(req.TeacherFeedback),
		Status:          sqlc.AnswerStatus(strings.TrimSpace(req.Status)),
		SubmittedAt:     shared.NullableTime(req.SubmittedAt),
		ReviewedAt:      shared.NullableTime(req.ReviewedAt),
		IsCorrect:       shared.NullableBool(isCorrect),
	})
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	if strings.TrimSpace(req.Status) == string(sqlc.AnswerStatusSubmitted) {
		updatedAnswer, aiErr := h.runAISubmissionReview(context.Background(), req.AssignmentID, studentID, answer)
		if aiErr != nil {
			log.Printf("AI review failed for assignment %d student %d: %v", req.AssignmentID, studentID, aiErr)
		} else {
			answer = updatedAnswer
		}
	}

	return c.Status(fiber.StatusCreated).JSON(mapStudentAnswer(answer))
}

func (h *Handler) runAISubmissionReview(parentCtx context.Context, assignmentID, studentID int64, currentAnswer sqlc.StudentAnswer) (sqlc.StudentAnswer, error) {
	if h.aiReview == nil || !h.aiReview.Enabled() {
		return currentAnswer, nil
	}

	dbCtx, cancel := shared.WithTimeout()
	assignment, err := h.queries.GetAssignmentByID(dbCtx, assignmentID)
	cancel()
	if err != nil {
		return currentAnswer, fmt.Errorf("load assignment for AI review: %w", err)
	}

	detailCtx, cancel := shared.WithTimeout()
	questions, err := h.queries.ListQuestionDetailsForAssignmentStudent(detailCtx, sqlc.ListQuestionDetailsForAssignmentStudentParams{
		AssignmentID: assignmentID,
		StudentID:    studentID,
	})
	cancel()
	if err != nil {
		return currentAnswer, fmt.Errorf("load assignment question details for AI review: %w", err)
	}

	input := buildAssignmentReviewInput(assignment, studentID, questions)
	if len(input.Questions) == 0 {
		return currentAnswer, nil
	}

	var result *aireview.AssignmentReviewResult
	var lastErr error
	for attempt := 1; attempt <= 3; attempt++ {
		attemptCtx, attemptCancel := context.WithTimeout(parentCtx, 45*time.Second)
		result, lastErr = h.aiReview.ReviewSubmission(attemptCtx, input)
		attemptCancel()
		if lastErr == nil {
			break
		}
		if attempt < 3 {
			time.Sleep(time.Duration(attempt) * time.Second)
		}
	}

	if lastErr != nil {
		fallbackMessage := fmt.Sprintf("AI review could not be completed automatically after 3 attempts. Please review manually. Last error: %v", lastErr)
		updateCtx, updateCancel := shared.WithTimeout()
		_, updateErr := h.queries.UpdateAssignmentAIReview(updateCtx, sqlc.UpdateAssignmentAIReviewParams{
			AssignmentID: assignmentID,
			StudentID:    studentID,
			AiFeedback:   shared.NullableText(&fallbackMessage),
			Column4:      "",
		})
		updateCancel()
		if updateErr != nil {
			return currentAnswer, fmt.Errorf("AI review failed (%v) and fallback update failed: %w", lastErr, updateErr)
		}
		return currentAnswer, lastErr
	}

	questionByID := make(map[int64]sqlc.ListQuestionDetailsForAssignmentStudentRow, len(questions))
	for _, question := range questions {
		if question.AnswerID.Valid {
			questionByID[question.QuestionID] = question
		}
	}

	updatedAnswer := currentAnswer
	for _, review := range result.Questions {
		question, ok := questionByID[review.QuestionID]
		if !ok || !question.AnswerID.Valid {
			continue
		}

		aiFeedback := review.AiFeedback
		issueReason := review.IssueReason
		correctnessScore := 1.0
		questionScore := 1.0
		understandingScore := review.UnderstandingScore
		confidence := review.Confidence

		updateCtx, updateCancel := shared.WithTimeout()
		answer, updateErr := h.queries.UpdateAnswerAIReview(updateCtx, sqlc.UpdateAnswerAIReviewParams{
			ID:                       question.AnswerID.Int64,
			AiFeedback:               shared.NullableText(&aiFeedback),
			ReviewNeedsAttention:     review.NeedsAttention,
			ReviewIssueReason:        shared.NullableText(&issueReason),
			ReviewCorrectnessScore:   mustNumeric(correctnessScore),
			ReviewUnderstandingScore: mustNumeric(understandingScore),
			ReviewQuestionScore:      mustNumeric(questionScore),
			ReviewConfidence:         mustNumeric(confidence),
		})
		updateCancel()
		if updateErr != nil {
			return currentAnswer, fmt.Errorf("persist AI answer review for answer %d: %w", question.AnswerID.Int64, updateErr)
		}

		if answer.ID == currentAnswer.ID {
			updatedAnswer = answer
		}
	}

	for _, question := range questions {
		if !question.AnswerID.Valid {
			continue
		}

		answerText := strings.TrimSpace(shared.TextValue(question.AnswerText))
		workingSteps := strings.TrimSpace(shared.TextValue(question.WorkingSteps))
		if answerText != "" || workingSteps != "" {
			continue
		}

		updateCtx, updateCancel := shared.WithTimeout()
		answer, updateErr := h.queries.UpdateAnswerAIReview(updateCtx, sqlc.UpdateAnswerAIReviewParams{
			ID:                       question.AnswerID.Int64,
			AiFeedback:               shared.NullableText(pointerToString("No answer was submitted for this question.")),
			ReviewNeedsAttention:     true,
			ReviewIssueReason:        shared.NullableText(pointerToString("No answer submitted.")),
			ReviewCorrectnessScore:   mustNumeric(1.0),
			ReviewUnderstandingScore: mustNumeric(0.0),
			ReviewQuestionScore:      mustNumeric(1.0),
			ReviewConfidence:         mustNumeric(1.0),
		})
		updateCancel()
		if updateErr != nil {
			return currentAnswer, fmt.Errorf("persist blank-answer AI review for answer %d: %w", question.AnswerID.Int64, updateErr)
		}

		if answer.ID == currentAnswer.ID {
			updatedAnswer = answer
		}
	}

	assignmentSummary := strings.TrimSpace(result.AssignmentSummary)
	nextStepOutcome := sqlc.NullAssignmentNextStepOutcome{}
	if result.RecommendedNextStep != "" {
		nextStepOutcome = sqlc.NullAssignmentNextStepOutcome{
			AssignmentNextStepOutcome: sqlc.AssignmentNextStepOutcome(result.RecommendedNextStep),
			Valid:                     true,
		}
	}

	updateCtx, updateCancel := shared.WithTimeout()
	_, err = h.queries.UpdateAssignmentAIReview(updateCtx, sqlc.UpdateAssignmentAIReviewParams{
		AssignmentID: assignmentID,
		StudentID:    studentID,
		AiFeedback:   shared.NullableText(&assignmentSummary),
		Column4:      nextStepOutcomeString(nextStepOutcome),
	})
	updateCancel()
	if err != nil {
		return currentAnswer, fmt.Errorf("persist assignment AI review: %w", err)
	}

	return updatedAnswer, nil
}

func buildAssignmentReviewInput(assignment sqlc.Assignment, studentID int64, questions []sqlc.ListQuestionDetailsForAssignmentStudentRow) aireview.AssignmentReviewInput {
	passThreshold := 6.0
	if value := shared.NumericPointer(assignment.PassThreshold); value != nil {
		passThreshold = *value
	}

	input := aireview.AssignmentReviewInput{
		AssignmentID:    assignment.ID,
		StudentID:       studentID,
		AssignmentTitle: assignment.Title,
		Instructions:    strings.TrimSpace(shared.TextValue(assignment.Instructions)),
		PassThreshold:   passThreshold,
		Questions:       make([]aireview.AssignmentQuestionInput, 0, len(questions)),
	}

	for _, question := range questions {
		answerText := strings.TrimSpace(shared.TextValue(question.AnswerText))
		workingSteps := strings.TrimSpace(shared.TextValue(question.WorkingSteps))
		answerStatus := ""
		if question.AnswerStatus.Valid {
			answerStatus = string(question.AnswerStatus.AnswerStatus)
		}

		input.Questions = append(input.Questions, aireview.AssignmentQuestionInput{
			QuestionID:    question.QuestionID,
			Position:      question.Position,
			Title:         question.Title,
			Prompt:        question.Prompt,
			Subject:       strings.TrimSpace(shared.TextValue(question.Subject)),
			Source:        strings.TrimSpace(shared.TextValue(question.Source)),
			CorrectAnswer: strings.TrimSpace(shared.TextValue(question.CorrectAnswer)),
			QuestionTags:  question.QuestionTags,
			SolveMode:     strings.TrimSpace(shared.TextValue(question.SolveMode)),
			AnswerText:    answerText,
			WorkingSteps:  workingSteps,
			IsCorrect:     shared.BoolPointer(question.IsCorrect),
			AnswerStatus:  answerStatus,
		})
	}

	return input
}

func mustNumeric(value float64) pgtype.Numeric {
	numeric, err := shared.NullableFloat64AsNumeric(&value)
	if err != nil {
		panic(err)
	}
	return numeric
}

func nextStepOutcomeString(value sqlc.NullAssignmentNextStepOutcome) string {
	if !value.Valid {
		return ""
	}

	return string(value.AssignmentNextStepOutcome)
}

func pointerToString(value string) *string {
	return &value
}

func (h *Handler) UpdateAnswerReview(c *fiber.Ctx) error {
	answerID, err := params.Int64PathParam(c, "answerId")
	if err != nil {
		return err
	}

	var req updateAnswerReviewRequest
	if err := c.BodyParser(&req); err != nil {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "Unable to parse request body")
	}

	status := strings.TrimSpace(req.Status)
	if status == "" || !shared.IsValidAnswerStatus(status) {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "A valid answer status is required")
	}

	for _, score := range []struct {
		name  string
		value *float64
	}{
		{name: "review_correctness_score", value: req.ReviewCorrectnessScore},
		{name: "review_understanding_score", value: req.ReviewUnderstandingScore},
		{name: "review_question_score", value: req.ReviewQuestionScore},
		{name: "review_confidence", value: req.ReviewConfidence},
	} {
		if score.value != nil && (*score.value < 0 || *score.value > 1) {
			return respond.Error(c, fiber.StatusBadRequest, "invalid_request", score.name+" must be between 0 and 1")
		}
	}

	reviewCorrectnessScore, err := shared.NullableFloat64AsNumeric(req.ReviewCorrectnessScore)
	if err != nil {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "review_correctness_score must be a valid number")
	}

	reviewUnderstandingScore, err := shared.NullableFloat64AsNumeric(req.ReviewUnderstandingScore)
	if err != nil {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "review_understanding_score must be a valid number")
	}

	reviewQuestionScore, err := shared.NullableFloat64AsNumeric(req.ReviewQuestionScore)
	if err != nil {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "review_question_score must be a valid number")
	}

	reviewConfidence, err := shared.NullableFloat64AsNumeric(req.ReviewConfidence)
	if err != nil {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "review_confidence must be a valid number")
	}

	reviewNeedsAttention := false
	if req.ReviewNeedsAttention != nil {
		reviewNeedsAttention = *req.ReviewNeedsAttention
	}

	reviewTags := req.ReviewTags
	if reviewTags == nil {
		reviewTags = []string{}
	}

	ctx, cancel := shared.WithTimeout()
	defer cancel()

	answer, err := h.queries.UpdateAnswerReview(ctx, sqlc.UpdateAnswerReviewParams{
		ID:                       answerID,
		Status:                   sqlc.AnswerStatus(status),
		ReviewNeedsAttention:     reviewNeedsAttention,
		ReviewIssueReason:        shared.NullableText(req.ReviewIssueReason),
		ReviewCorrectnessScore:   reviewCorrectnessScore,
		ReviewUnderstandingScore: reviewUnderstandingScore,
		ReviewQuestionScore:      reviewQuestionScore,
		ReviewConfidence:         reviewConfidence,
		ReviewTags:               reviewTags,
	})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return respond.Error(c, fiber.StatusNotFound, "not_found", "Answer not found")
		}
		return respond.DatabaseError(c, err)
	}

	return c.JSON(mapStudentAnswer(answer))
}

func mapStudentAnswer(answer sqlc.StudentAnswer) StudentAnswerResponse {
	return StudentAnswerResponse{
		ID:                       answer.ID,
		AssignmentID:             answer.AssignmentID,
		QuestionID:               answer.QuestionID,
		StudentID:                answer.StudentID,
		AnswerText:               shared.TextPointer(answer.AnswerText),
		IsCorrect:                shared.BoolPointer(answer.IsCorrect),
		SolveMode:                answer.SolveMode,
		WorkingSteps:             shared.TextPointer(answer.WorkingSteps),
		AiFeedback:               shared.TextPointer(answer.AiFeedback),
		TeacherFeedback:          shared.TextPointer(answer.TeacherFeedback),
		Status:                   string(answer.Status),
		ReviewNeedsAttention:     answer.ReviewNeedsAttention,
		ReviewIssueReason:        shared.TextPointer(answer.ReviewIssueReason),
		ReviewCorrectnessScore:   shared.NumericPointer(answer.ReviewCorrectnessScore),
		ReviewUnderstandingScore: shared.NumericPointer(answer.ReviewUnderstandingScore),
		ReviewQuestionScore:      shared.NumericPointer(answer.ReviewQuestionScore),
		ReviewConfidence:         shared.NumericPointer(answer.ReviewConfidence),
		ReviewTags:               answer.ReviewTags,
		SubmittedAt:              shared.TimePointer(answer.SubmittedAt),
		ReviewedAt:               shared.TimePointer(answer.ReviewedAt),
		CreatedAt:                shared.TimePointer(answer.CreatedAt),
		UpdatedAt:                shared.TimePointer(answer.UpdatedAt),
	}
}

func isValidSolveMode(value string) bool {
	switch value {
	case "just_answer", "step_by_step", "solve_together", "handwritten":
		return true
	default:
		return false
	}
}

func compareAnswer(correctAnswer pgtype.Text, studentAnswer *string) *bool {
	if !correctAnswer.Valid {
		return nil
	}

	canonical := normalizeComparableAnswer(correctAnswer.String)
	if canonical == "" {
		return nil
	}

	if studentAnswer == nil {
		return nil
	}

	student := normalizeComparableAnswer(*studentAnswer)
	if student == "" {
		return nil
	}

	result := student == canonical
	return &result
}

func normalizeComparableAnswer(value string) string {
	trimmed := strings.TrimSpace(strings.ToLower(value))
	if trimmed == "" {
		return ""
	}

	return strings.Join(strings.Fields(trimmed), " ")
}
