package assignments

import (
	"boostai-backend/internal/aireview"
	"boostai-backend/internal/assignmentgen"
	"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"
	"errors"
	"fmt"
	"strings"

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

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

const fixedPassThreshold = 6.0

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

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

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

	assignments, err := h.queries.ListAssignmentsByTeacher(ctx, teacherID)
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	items := make([]AssignmentResponse, 0, len(assignments))
	for _, assignment := range assignments {
		items = append(items, mapAssignment(assignment))
	}

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

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

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

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

	items := make([]AssignmentResponse, 0, len(assignments))
	for _, assignment := range assignments {
		items = append(items, mapAssignment(assignment))
	}

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

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

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

	assignment, err := h.queries.GetAssignmentByID(ctx, assignmentID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return respond.Error(c, fiber.StatusNotFound, "not_found", "Assignment not found")
		}
		return respond.DatabaseError(c, err)
	}

	return c.JSON(mapAssignment(assignment))
}

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

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

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

	items := make([]AssignmentQuestionResponse, 0, len(questions))
	for _, question := range questions {
		items = append(items, mapAssignmentQuestion(question))
	}

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

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

	studentID, err := params.Int64PathParam(c, "studentId")
	if err != nil {
		return err
	}

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

	rows, err := h.queries.ListQuestionDetailsForAssignmentStudent(ctx, sqlc.ListQuestionDetailsForAssignmentStudentParams{
		AssignmentID: assignmentID,
		StudentID:    studentID,
	})
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	items := make([]AssignmentStudentQuestionDetailResponse, 0, len(rows))
	for _, row := range rows {
		items = append(items, mapAssignmentStudentQuestionDetail(row, studentID))
	}

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

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

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

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

	return c.JSON(mapAssignmentReviewSummary(summary))
}

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

	studentID, err := params.Int64PathParam(c, "studentId")
	if err != nil {
		return err
	}

	ctx, cancel := shared.WithTimeout()
	row, err := h.queries.GetAssignmentRedoPlan(ctx, sqlc.GetAssignmentRedoPlanParams{
		AssignmentID: assignmentID,
		StudentID:    studentID,
	})
	cancel()
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return respond.Error(c, fiber.StatusNotFound, "not_found", "Assignment student record not found")
		}
		return respond.DatabaseError(c, err)
	}

	summary, err := h.buildStudentWeaknessSummary(studentID)
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	response := AssignmentRedoPlanResponse{
		AssignmentID:        assignmentID,
		StudentID:           studentID,
		RedoPlanGeneratedAt: shared.TimePointer(row.RedoPlanGeneratedAt),
		WeaknessSummary:     mapWeaknessSummary(studentID, summary),
	}

	if row.RedoPlan.Valid {
		stored, err := parseStoredRedoPlan(row.RedoPlan.String)
		if err != nil {
			response.Error = fmt.Sprintf("stored redo plan could not be parsed: %v", err)
		} else {
			response.TeacherFeedback = emptyStringPointer(stored.TeacherFeedback)
			response.Error = stored.Error
			response.Plan = stored.Plan
			if len(stored.WeaknessSummary.TopicScores) > 0 || len(stored.WeaknessSummary.WeakTags) > 0 || len(stored.WeaknessSummary.RecentIssues) > 0 {
				response.WeaknessSummary = mapWeaknessSummary(studentID, stored.WeaknessSummary)
			}
		}
	}

	return c.JSON(response)
}

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

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

	teacherID := authmw.CurrentUserID(c)
	title := strings.TrimSpace(req.Title)
	if req.ClassroomID == 0 || teacherID == 0 || title == "" {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "classroom_id, teacher authentication, and title are required")
	}

	passThreshold, err := shared.NullableFloat64AsNumeric(pointerToFloat64(fixedPassThreshold))
	if err != nil {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "pass_threshold must be a valid number")
	}

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

	assignment, err := h.queries.GetAssignmentByID(ctx, assignmentID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return respond.Error(c, fiber.StatusNotFound, "not_found", "Assignment not found")
		}
		return respond.DatabaseError(c, err)
	}

	if assignment.TeacherID != teacherID {
		return respond.Error(c, fiber.StatusForbidden, "forbidden", "You can only edit your own draft assignments")
	}

	if assignment.Status != sqlc.AssignmentStatusDraft {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "Only draft assignments can be edited here")
	}

	classrooms, err := h.queries.ListClassroomsByTeacher(ctx, teacherID)
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	classroomAllowed := false
	for _, classroom := range classrooms {
		if classroom.ID == req.ClassroomID {
			classroomAllowed = true
			break
		}
	}

	if !classroomAllowed {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "Choose one of your classrooms for this draft")
	}

	updatedAssignment, err := h.queries.UpdateAssignmentDraft(ctx, sqlc.UpdateAssignmentDraftParams{
		ID:            assignmentID,
		ClassroomID:   req.ClassroomID,
		Title:         title,
		Instructions:  shared.NullableText(req.Instructions),
		PassThreshold: passThreshold,
		DueAt:         shared.NullableTime(req.DueAt),
	})
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	return c.JSON(mapAssignment(updatedAssignment))
}

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

	studentID, err := params.Int64PathParam(c, "studentId")
	if err != nil {
		return err
	}

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

	passStatusOverrideValue := ""
	if req.PassStatusOverride != nil {
		passStatusOverrideValue = strings.TrimSpace(*req.PassStatusOverride)
		if passStatusOverrideValue != "" && !isValidAssignmentPassStatus(passStatusOverrideValue) {
			return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "pass_status_override must be pending, pass, no_pass, or empty")
		}
	}

	nextStepOutcomeValue := ""
	if req.NextStepOutcome != nil {
		nextStepOutcomeValue = strings.TrimSpace(*req.NextStepOutcome)
		if nextStepOutcomeValue != "" && !isValidAssignmentNextStepOutcome(nextStepOutcomeValue) {
			return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "next_step_outcome must be redo, accept, support, or empty")
		}
	}

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

	row, err := h.queries.UpdateAssignmentTeacherFeedback(ctx, sqlc.UpdateAssignmentTeacherFeedbackParams{
		AssignmentID:    assignmentID,
		StudentID:       studentID,
		TeacherFeedback: shared.NullableText(req.TeacherFeedback),
		Column4:         passStatusOverrideValue,
		Column5:         nextStepOutcomeValue,
	})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return respond.Error(c, fiber.StatusNotFound, "not_found", "Assignment student record not found")
		}
		return respond.DatabaseError(c, err)
	}

	if nextStepOutcomeValue == string(sqlc.AssignmentNextStepOutcomeRedo) {
		if err := h.generateAndStoreRedoPlan(assignmentID, studentID, strings.TrimSpace(shared.TextValue(row.TeacherFeedback))); err != nil {
			fmt.Printf("redo plan generation failed for assignment %d student %d: %v\n", assignmentID, studentID, err)
		}
	} else {
		clearCtx, clearCancel := shared.WithTimeout()
		_, clearErr := h.queries.UpdateAssignmentRedoPlan(clearCtx, sqlc.UpdateAssignmentRedoPlanParams{
			AssignmentID: assignmentID,
			StudentID:    studentID,
			Column3:      "",
		})
		clearCancel()
		if clearErr != nil && !errors.Is(clearErr, pgx.ErrNoRows) {
			fmt.Printf("redo plan clear failed for assignment %d student %d: %v\n", assignmentID, studentID, clearErr)
		}
	}

	var passStatusOverride *string
	if row.PassStatusOverride.Valid {
		status := string(row.PassStatusOverride.AssignmentPassStatus)
		passStatusOverride = &status
	}

	var nextStepOutcome *string
	if row.NextStepOutcome.Valid {
		outcome := string(row.NextStepOutcome.AssignmentNextStepOutcome)
		nextStepOutcome = &outcome
	}

	return c.JSON(fiber.Map{
		"assignment_id":        assignmentID,
		"student_id":           studentID,
		"ai_feedback":          shared.TextPointer(row.AiFeedback),
		"teacher_feedback":     shared.TextPointer(row.TeacherFeedback),
		"overall_score":        shared.NumericPointer(row.OverallScore),
		"pass_threshold":       shared.NumericPointer(row.PassThreshold),
		"next_step_outcome":    nextStepOutcome,
		"pass_status_override": passStatusOverride,
		"pass_status":          string(row.PassStatus),
	})
}

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

	teacherID := authmw.CurrentUserID(c)
	if teacherID == 0 {
		return respond.Error(c, fiber.StatusUnauthorized, "unauthorized", "Teacher authentication is required")
	}

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

	assignment, err := h.queries.GetAssignmentByID(ctx, assignmentID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return respond.Error(c, fiber.StatusNotFound, "not_found", "Assignment not found")
		}
		return respond.DatabaseError(c, err)
	}

	if assignment.TeacherID != teacherID {
		return respond.Error(c, fiber.StatusForbidden, "forbidden", "You can only close your own assignments")
	}

	if assignment.Status == sqlc.AssignmentStatusDraft {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "Draft assignments cannot be closed")
	}

	if assignment.Status == sqlc.AssignmentStatusClosed {
		return c.JSON(mapAssignment(assignment))
	}

	queue, err := h.queries.ListAssignmentReviewQueue(ctx, sqlc.ListAssignmentReviewQueueParams{
		AssignmentID: assignmentID,
		Column2:      "",
	})
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	readiness := buildAssignmentCloseReadiness(queue)
	if !readiness.CanClose {
		return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
			"error":    "assignment_not_ready_to_close",
			"message":  "This assignment still has open review blockers.",
			"blockers": readiness.Blockers,
		})
	}

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

	return c.JSON(mapAssignment(closedAssignment))
}

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

	statusFilter := strings.TrimSpace(c.Query("status"))
	if statusFilter != "" && !shared.IsValidAnswerStatus(statusFilter) {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "Invalid review status filter")
	}

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

	rows, err := h.queries.ListAssignmentReviewQueue(ctx, sqlc.ListAssignmentReviewQueueParams{
		AssignmentID: assignmentID,
		Column2:      statusFilter,
	})
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	items := make([]AssignmentReviewQueueItemResponse, 0, len(rows))
	for _, row := range rows {
		items = append(items, mapAssignmentReviewQueueItem(row))
	}

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

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

	teacherID := authmw.CurrentUserID(c)
	if req.ClassroomID == 0 || teacherID == 0 || strings.TrimSpace(req.Title) == "" || strings.TrimSpace(req.Status) == "" {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "classroom_id, teacher authentication, title, and status are required")
	}

	passThreshold, err := shared.NullableFloat64AsNumeric(pointerToFloat64(fixedPassThreshold))
	if err != nil {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "pass_threshold must be a valid number")
	}

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

	assignment, err := h.queries.CreateAssignment(ctx, sqlc.CreateAssignmentParams{
		ClassroomID:   req.ClassroomID,
		TeacherID:     teacherID,
		Title:         strings.TrimSpace(req.Title),
		Instructions:  shared.NullableText(req.Instructions),
		PassThreshold: passThreshold,
		Status:        sqlc.AssignmentStatus(strings.TrimSpace(req.Status)),
		DueAt:         shared.NullableTime(req.DueAt),
		PublishedAt:   shared.NullableTime(req.PublishedAt),
	})
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	return c.Status(fiber.StatusCreated).JSON(mapAssignment(assignment))
}

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

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

	if req.StudentID == 0 {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "student_id is required")
	}

	teacherID := authmw.CurrentUserID(c)
	if teacherID == 0 {
		return respond.Error(c, fiber.StatusUnauthorized, "unauthorized", "Teacher authentication is required")
	}

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

	assignment, err := h.queries.GetAssignmentByID(ctx, assignmentID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return respond.Error(c, fiber.StatusNotFound, "not_found", "Assignment not found")
		}
		return respond.DatabaseError(c, err)
	}

	if assignment.TeacherID != teacherID {
		return respond.Error(c, fiber.StatusForbidden, "forbidden", "You can only assign students to your own assignments")
	}

	err = h.queries.AssignStudentToAssignment(ctx, sqlc.AssignStudentToAssignmentParams{
		AssignmentID: assignmentID,
		StudentID:    req.StudentID,
	})
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	response := fiber.Map{
		"status":        "ok",
		"assignment_id": assignmentID,
		"student_id":    req.StudentID,
	}

	if req.MixedGeneration != nil {
		if h.assignmentGenerator == nil {
			cleanupErr := h.queries.DeleteAssignmentAssignee(ctx, sqlc.DeleteAssignmentAssigneeParams{
				AssignmentID: assignmentID,
				StudentID:    req.StudentID,
			})
			if cleanupErr != nil {
				return respond.DatabaseError(c, cleanupErr)
			}
			return respond.Error(c, fiber.StatusServiceUnavailable, "generator_unavailable", "Assignment generator is not configured")
		}

		generated, generationErr := h.generateMixedStudentQuestionsForAssignmentStudent(ctx, assignmentID, req.StudentID, teacherID, req.MixedGeneration)
		if generationErr != nil {
			cleanupErr := h.queries.DeleteAssignmentAssignee(ctx, sqlc.DeleteAssignmentAssigneeParams{
				AssignmentID: assignmentID,
				StudentID:    req.StudentID,
			})
			if cleanupErr != nil {
				return respond.DatabaseError(c, cleanupErr)
			}
			if apiErr, ok := generationErr.(*assignmentAPIError); ok {
				return respond.Error(c, apiErr.status, apiErr.code, apiErr.message)
			}
			return respond.DatabaseError(c, generationErr)
		}

		response["mixed_generation"] = generated
	}

	return c.Status(fiber.StatusCreated).JSON(response)
}

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

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

	if req.QuestionID == 0 || req.Position <= 0 {
		return respond.Error(c, fiber.StatusBadRequest, "invalid_request", "question_id and positive position are required")
	}

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

	err = h.queries.AddQuestionToAssignment(ctx, sqlc.AddQuestionToAssignmentParams{
		AssignmentID: assignmentID,
		QuestionID:   req.QuestionID,
		Position:     req.Position,
	})
	if err != nil {
		return respond.DatabaseError(c, err)
	}

	return c.Status(fiber.StatusCreated).JSON(fiber.Map{
		"status":        "ok",
		"assignment_id": assignmentID,
		"question_id":   req.QuestionID,
		"position":      req.Position,
	})
}

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

	studentID, err := params.Int64PathParam(c, "studentId")
	if err != nil {
		return err
	}

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

	teacherID := authmw.CurrentUserID(c)
	if teacherID == 0 {
		return respond.Error(c, fiber.StatusUnauthorized, "unauthorized", "Teacher authentication is required")
	}

	if h.assignmentGenerator == nil {
		return respond.Error(c, fiber.StatusServiceUnavailable, "generator_unavailable", "Assignment generator is not configured")
	}

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

	response, generationErr := h.generateMixedStudentQuestionsForAssignmentStudent(ctx, assignmentID, studentID, teacherID, &req)
	if generationErr != nil {
		if apiErr, ok := generationErr.(*assignmentAPIError); ok {
			return respond.Error(c, apiErr.status, apiErr.code, apiErr.message)
		}
		return respond.DatabaseError(c, generationErr)
	}

	return c.Status(fiber.StatusCreated).JSON(response)
}
