package assignmentgen

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"boostai-backend/internal/questiongen"
	"boostai-backend/internal/sqlc"

	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
)

func (s *Service) validateGenerateRequest(params GenerateStudentQuestionSetParams) error {
	if s == nil || s.db == nil || s.db.Pool == nil {
		return errors.New("assignment question generator database is not configured")
	}
	if s.generator == nil {
		return errors.New("assignment question generator is not configured")
	}
	if params.AssignmentID <= 0 || params.StudentID <= 0 || params.TeacherID <= 0 {
		return errors.New("assignment_id, student_id, and teacher_id are required")
	}
	return validatePlanItems(params.Plan)
}

func validatePlanItems(plan []PlanItem) error {
	if len(plan) == 0 {
		return errors.New("at least one generation plan item is required")
	}

	for _, item := range plan {
		if item.Count <= 0 {
			return fmt.Errorf("generation count must be positive for bucket %q", item.SourceBucket)
		}
		if strings.TrimSpace(string(item.SourceBucket)) == "" {
			return errors.New("source bucket is required for every generation plan item")
		}
	}

	return nil
}

func validateAssignmentOwnership(ctx context.Context, queries *sqlc.Queries, assignmentID, teacherID int64) error {
	assignment, err := queries.GetAssignmentByID(ctx, assignmentID)
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return fmt.Errorf("assignment %d not found", assignmentID)
		}
		return err
	}
	if assignment.TeacherID != teacherID {
		return fmt.Errorf("assignment %d does not belong to teacher %d", assignmentID, teacherID)
	}
	return nil
}

func validateStudentAssignment(ctx context.Context, queries *sqlc.Queries, assignmentID, studentID int64) error {
	_, err := queries.GetAssignmentAssignee(ctx, sqlc.GetAssignmentAssigneeParams{
		AssignmentID: assignmentID,
		StudentID:    studentID,
	})
	if err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return fmt.Errorf("student %d is not assigned to assignment %d", studentID, assignmentID)
		}
		return err
	}
	return nil
}

func clearStudentQuestionMappings(ctx context.Context, queries *sqlc.Queries, assignmentID, studentID int64) error {
	return queries.DeleteAssignmentStudentQuestions(ctx, sqlc.DeleteAssignmentStudentQuestionsParams{
		AssignmentID: assignmentID,
		StudentID:    studentID,
	})
}

func normalizeQuestionDefaults(params GenerateStudentQuestionSetParams) (sqlc.QuestionStatus, string) {
	questionStatus := params.QuestionStatus
	if questionStatus == "" {
		questionStatus = sqlc.QuestionStatusDraft
	}

	questionSource := strings.TrimSpace(params.QuestionSource)
	if questionSource == "" {
		questionSource = defaultQuestionSource
	}

	return questionStatus, questionSource
}

func (s *Service) generateAndStorePlan(
	ctx context.Context,
	queries *sqlc.Queries,
	params GenerateStudentQuestionSetParams,
	questionStatus sqlc.QuestionStatus,
	questionSource string,
) ([]StoredStudentQuestion, error) {
	stored := make([]StoredStudentQuestion, 0)
	position := int32(1)

	for _, item := range params.Plan {
		generatedQuestions, usedSeed, err := s.generator.Generate(questiongen.GenerateParams{
			Topic:      item.Topic,
			Difficulty: item.Difficulty,
			Count:      item.Count,
			Seed:       item.Seed,
		})
		if err != nil {
			return nil, err
		}

		storedBatch, nextPosition, err := storeGeneratedQuestionBatch(
			ctx,
			queries,
			params,
			item,
			generatedQuestions,
			questionStatus,
			questionSource,
			usedSeed,
			position,
		)
		if err != nil {
			return nil, err
		}

		stored = append(stored, storedBatch...)
		position = nextPosition
	}

	return stored, nil
}

func storeGeneratedQuestionBatch(
	ctx context.Context,
	queries *sqlc.Queries,
	params GenerateStudentQuestionSetParams,
	item PlanItem,
	generatedQuestions []questiongen.GeneratedQuestion,
	questionStatus sqlc.QuestionStatus,
	questionSource string,
	usedSeed int64,
	startPosition int32,
) ([]StoredStudentQuestion, int32, error) {
	stored := make([]StoredStudentQuestion, 0, len(generatedQuestions))
	position := startPosition

	for _, generated := range generatedQuestions {
		storedQuestion, err := storeGeneratedQuestion(
			ctx,
			queries,
			params,
			item,
			generated,
			questionStatus,
			questionSource,
			usedSeed,
			position,
		)
		if err != nil {
			return nil, startPosition, err
		}

		stored = append(stored, storedQuestion)
		position++
	}

	return stored, position, nil
}

func storeGeneratedQuestion(
	ctx context.Context,
	queries *sqlc.Queries,
	params GenerateStudentQuestionSetParams,
	item PlanItem,
	generated questiongen.GeneratedQuestion,
	questionStatus sqlc.QuestionStatus,
	questionSource string,
	usedSeed int64,
	position int32,
) (StoredStudentQuestion, error) {
	question, err := queries.CreateQuestion(ctx, sqlc.CreateQuestionParams{
		AuthorTeacherID: params.TeacherID,
		Title:           generated.Title,
		Prompt:          generated.Prompt,
		Topic:           nullableQuestionTopic(item.Topic),
		Subject:         textValue(firstNonEmpty(params.Subject, questionTopicLabel(item.Topic))),
		Difficulty:      nullableQuestionDifficulty(item.Difficulty),
		Source:          textValue(questionSource),
		Status:          questionStatus,
		CorrectAnswer:   textValue(generated.CorrectAnswer),
	})
	if err != nil {
		return StoredStudentQuestion{}, err
	}

	tags := mergeTags(generated.Tags, string(item.SourceBucket), questionSource)
	if err := attachQuestionTags(ctx, queries, question.ID, tags); err != nil {
		return StoredStudentQuestion{}, err
	}

	mapping, err := queries.AddAssignmentStudentQuestion(ctx, sqlc.AddAssignmentStudentQuestionParams{
		AssignmentID:     params.AssignmentID,
		StudentID:        params.StudentID,
		QuestionID:       question.ID,
		Position:         position,
		SourceBucket:     string(item.SourceBucket),
		SourceTopic:      nullableQuestionTopic(item.Topic),
		SourceDifficulty: nullableQuestionDifficulty(item.Difficulty),
		GeneratorSeed:    pgtype.Int8{Int64: usedSeed, Valid: true},
	})
	if err != nil {
		return StoredStudentQuestion{}, err
	}

	return StoredStudentQuestion{
		Mapping:      mapping,
		Question:     question,
		Tags:         tags,
		UsedSeed:     usedSeed,
		SourceBucket: string(item.SourceBucket),
	}, nil
}

func attachQuestionTags(ctx context.Context, queries *sqlc.Queries, questionID int64, tagNames []string) error {
	for _, tagName := range tagNames {
		tag, err := queries.CreateTag(ctx, tagName)
		if err != nil {
			return err
		}
		if err := queries.AttachTagToQuestion(ctx, sqlc.AttachTagToQuestionParams{
			QuestionID: questionID,
			TagID:      tag.ID,
		}); err != nil {
			return err
		}
	}

	return nil
}
