import { prisma } from '../config/database';
import { Prisma } from '@prisma/client';
import { config } from '../config';
import { logger } from '../config/logger';
import { deleteFile, generateFilePath, uploadFile, getPresignedUrl, listObjects, getVideoFolder, getAttachmentsFolder } from '../config/minio';
import { UnauthorizedError, ValidationError, ForbiddenError, NotFoundError } from '../middleware/errorHandler';
import jwt from 'jsonwebtoken';
import {
    LessonData,
    ChapterData,
    CreateLessonInput,
    CreateChapterInput,
    UpdateChapterInput,
    ChaptersRequest,
    DeleteChapterRequest,
    ReorderChapterRequest,
    ListChaptersResponse,
    CreateChapterResponse,
    UpdateChapterResponse,
    DeleteChapterResponse,
    ReorderChapterResponse,
    GetLessonRequest,
    UpdateLessonRequest,
    DeleteLessonRequest,
    ReorderLessonsRequest,
    GetLessonResponse,
    CreateLessonResponse,
    UpdateLessonResponse,
    DeleteLessonResponse,
    ReorderLessonsResponse,
    AddQuestionInput,
    AddQuestionResponse,
    UpdateQuestionInput,
    UpdateQuestionResponse,
    DeleteQuestionInput,
    DeleteQuestionResponse,
    QuizQuestionData,
    ReorderQuestionInput,
    ReorderQuestionResponse,
    UploadVideoInput,
    UpdateVideoInput,
    SetYouTubeVideoInput,
    YouTubeVideoResponse,
    UploadAttachmentInput,
    DeleteAttachmentInput,
    VideoOperationResponse,
    AttachmentOperationResponse,
    DeleteAttachmentResponse,
    LessonAttachmentData,
    UpdateQuizInput,
    UpdateQuizResponse,
    QuizData,
} from "../types/ChaptersLesson.typs";
import { CoursesInstructorService } from './CoursesInstructor.service';

/**
 * ตรวจสอบสิทธิ์เข้าถึง Course (สำหรับทั้ง Instructor และ Student)
 * Returns: { hasAccess: boolean, role: 'INSTRUCTOR' | 'STUDENT' | null, userId: number }
 */
async function validateCourseAccess(token: string, course_id: number): Promise<{
    hasAccess: boolean;
    role: 'INSTRUCTOR' | 'STUDENT' | null;
    userId: number;
}> {
    const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
    const userId = decodedToken.id;

    const user = await prisma.user.findUnique({ where: { id: userId } });
    if (!user) {
        throw new UnauthorizedError('Invalid token');
    }

    // Check if user is an instructor for this course
    const courseInstructor = await prisma.courseInstructor.findFirst({
        where: { course_id, user_id: userId }
    });
    if (courseInstructor) {
        return { hasAccess: true, role: 'INSTRUCTOR', userId };
    }

    // Check if user is enrolled in this course
    const enrollment = await prisma.enrollment.findFirst({
        where: {
            course_id,
            user_id: userId,
            status: { in: ['ENROLLED', 'IN_PROGRESS', 'COMPLETED'] }
        }
    });
    if (enrollment) {
        return { hasAccess: true, role: 'STUDENT', userId };
    }

    return { hasAccess: false, role: null, userId };
}

export class ChaptersLessonService {
    async listChapters(request: ChaptersRequest): Promise<ListChaptersResponse> {
        try {
            const { token, course_id } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) {
                throw new UnauthorizedError('Invalid token');
            }
            const chapters = await prisma.chapter.findMany({
                where: { course_id }, orderBy: { sort_order: 'asc' },
                include: { lessons: { orderBy: { sort_order: 'asc' } } }
            });
            return { code: 200, message: 'Chapters fetched successfully', data: chapters as ChapterData[], total: chapters.length };
        } catch (error) {
            logger.error(`Error fetching chapters: ${error}`);
            throw error;
        }
    }

    async createChapter(request: CreateChapterInput): Promise<CreateChapterResponse> {
        try {
            const { token, course_id, title, description, sort_order } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);
            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) {
                throw new UnauthorizedError('Invalid token');
            }
            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) {
                throw new ForbiddenError('You are not permitted to create chapter');
            }
            const chapter = await prisma.chapter.create({ data: { course_id, title, description, sort_order } });
            return { code: 200, message: 'Chapter created successfully', data: chapter as ChapterData };
        } catch (error) {
            logger.error(`Error creating chapter: ${error}`);
            throw error;
        }
    }

    async updateChapter(request: UpdateChapterInput): Promise<UpdateChapterResponse> {
        try {
            const { token, course_id, chapter_id, title, description, sort_order } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);
            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) {
                throw new UnauthorizedError('Invalid token');
            }
            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) {
                throw new ForbiddenError('You are not permitted to update chapter');
            }
            const chapter = await prisma.chapter.update({ where: { id: chapter_id }, data: { title, description, sort_order } });
            return { code: 200, message: 'Chapter updated successfully', data: chapter as ChapterData };
        } catch (error) {
            logger.error(`Error updating chapter: ${error}`);
            throw error;
        }
    }

    async deleteChapter(request: DeleteChapterRequest): Promise<DeleteChapterResponse> {
        try {
            const { token, course_id, chapter_id } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);
            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) {
                throw new UnauthorizedError('Invalid token');
            }
            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) {
                throw new ForbiddenError('You are not permitted to delete chapter');
            }
            await prisma.chapter.delete({ where: { id: chapter_id } });

            // Normalize sort_order for remaining chapters (fill gaps)
            await this.normalizeChapterSortOrder(course_id);

            return { code: 200, message: 'Chapter deleted successfully' };
        } catch (error) {
            logger.error(`Error deleting chapter: ${error}`);
            throw error;
        }
    }

    async reorderChapter(request: ReorderChapterRequest): Promise<ReorderChapterResponse> {
        try {
            const { token, course_id, chapter_id, sort_order: newSortOrder } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);
            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) {
                throw new UnauthorizedError('Invalid token');
            }
            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) {
                throw new ForbiddenError('You are not permitted to reorder chapter');
            }

            // Get current chapter to find its current sort_order
            const currentChapter = await prisma.chapter.findUnique({ where: { id: chapter_id } });
            if (!currentChapter) {
                throw new NotFoundError('Chapter not found');
            }
            // Validate chapter belongs to the specified course
            if (currentChapter.course_id !== course_id) {
                throw new NotFoundError('Chapter not found in this course');
            }

            // First, normalize sort_order to fix any gaps or duplicates
            await this.normalizeChapterSortOrder(course_id);

            // Re-fetch the chapter to get updated sort_order after normalization
            const normalizedChapter = await prisma.chapter.findUnique({ where: { id: chapter_id } });
            if (!normalizedChapter) throw new NotFoundError('Chapter not found');

            const oldSortOrder = normalizedChapter.sort_order;

            // Get total chapter count to validate sort_order (1-based)
            const chapterCount = await prisma.chapter.count({ where: { course_id } });
            const validNewSortOrder = Math.max(1, Math.min(newSortOrder, chapterCount));

            // If same position, no need to reorder
            if (oldSortOrder === validNewSortOrder) {
                const chapters = await prisma.chapter.findMany({
                    where: { course_id },
                    orderBy: { sort_order: 'asc' }
                });
                return { code: 200, message: 'Chapter reordered successfully', data: chapters as ChapterData[] };
            }

            // Shift other chapters to make room for the insert
            if (oldSortOrder > validNewSortOrder) {
                // Moving up: shift chapters between newSortOrder and oldSortOrder-1 down by 1
                await prisma.chapter.updateMany({
                    where: {
                        course_id,
                        sort_order: { gte: validNewSortOrder, lt: oldSortOrder }
                    },
                    data: { sort_order: { increment: 1 } }
                });
            } else {
                // Moving down: shift chapters between oldSortOrder+1 and newSortOrder up by 1
                await prisma.chapter.updateMany({
                    where: {
                        course_id,
                        sort_order: { gt: oldSortOrder, lte: validNewSortOrder }
                    },
                    data: { sort_order: { decrement: 1 } }
                });
            }

            // Update the target chapter to the new position
            await prisma.chapter.update({ where: { id: chapter_id }, data: { sort_order: validNewSortOrder } });

            // Fetch all chapters with updated order
            const chapters = await prisma.chapter.findMany({
                where: { course_id },
                orderBy: { sort_order: 'asc' }
            });

            return { code: 200, message: 'Chapter reordered successfully', data: chapters as ChapterData[] };
        } catch (error) {
            logger.error(`Error reordering chapter: ${error}`);
            throw error;
        }
    }

    /**
     * สร้างบทเรียนเปล่า (ยังไม่มีเนื้อหา)
     * Create an empty lesson with basic information (no content yet)
     * สำหรับ QUIZ type จะสร้าง Quiz shell ไว้ด้วย แต่ยังไม่มีคำถาม
     */
    async createLesson(request: CreateLessonInput): Promise<CreateLessonResponse> {
        try {
            const { token, course_id, chapter_id, title, content, type, sort_order } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);
            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) {
                throw new UnauthorizedError('Invalid token');
            }
            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) {
                throw new ForbiddenError('You are not permitted to create lesson');
            }

            // Create the lesson
            const lesson = await prisma.lesson.create({
                data: { chapter_id, title, content, type, sort_order }
            });

            // If QUIZ type, create empty Quiz shell
            if (type === 'QUIZ') {
                const userId = decodedToken.id;

                await prisma.quiz.create({
                    data: {
                        lesson_id: lesson.id,
                        title: title, // Use lesson title as quiz title
                        passing_score: 60,
                        shuffle_questions: false,
                        shuffle_choices: false,
                        show_answers_after_completion: true,
                        created_by: userId,
                    }
                });

                // Fetch complete lesson with quiz
                const completeLesson = await prisma.lesson.findUnique({
                    where: { id: lesson.id },
                    include: { quiz: true }
                });
                return { code: 200, message: 'Lesson created successfully', data: completeLesson as LessonData };
            }

            return { code: 200, message: 'Lesson created successfully', data: lesson as LessonData };
        } catch (error) {
            logger.error(`Error creating lesson: ${error}`);
            throw error;
        }
    }

    /**
     * ดึงข้อมูลบทเรียนพร้อม attachments และ quiz
     * Get lesson with attachments and quiz (if QUIZ type)
     */
    async getLesson(request: GetLessonRequest): Promise<GetLessonResponse> {
        try {
            const { token, course_id, lesson_id } = request;

            // Check access for both instructor and enrolled student
            const access = await validateCourseAccess(token, course_id);
            if (!access.hasAccess) {
                throw new ForbiddenError('You do not have access to this course');
            }

            const lesson = await prisma.lesson.findUnique({
                where: { id: lesson_id },
                include: {
                    attachments: { orderBy: { sort_order: 'asc' } },
                    quiz: {
                        include: {
                            questions: {
                                orderBy: { sort_order: 'asc' },
                                include: { choices: { orderBy: { sort_order: 'asc' } } }
                            }
                        }
                    }
                }
            });

            if (!lesson) throw new NotFoundError('Lesson not found');

            // Verify lesson belongs to the course
            const chapter = await prisma.chapter.findUnique({ where: { id: lesson.chapter_id } });
            if (!chapter || chapter.course_id !== course_id) {
                throw new NotFoundError('Lesson not found in this course');
            }

            // For students, check if lesson is published
            if (access.role === 'STUDENT' && !lesson.is_published) {
                throw new ForbiddenError('This lesson is not available yet');
            }

            // Get video URL - check for YouTube or MinIO
            let video_url: string | null = null;
            const videoAttachment = await prisma.lessonAttachment.findFirst({
                where: { lesson_id, sort_order: 0 }
            });

            if (videoAttachment) {
                if (videoAttachment.mime_type === 'video/youtube') {
                    // YouTube video - build URL from video ID stored in file_path
                    video_url = `https://www.youtube.com/watch?v=${videoAttachment.file_path}`;
                } else {
                    // MinIO video - get presigned URL
                    try {
                        video_url = await getPresignedUrl(videoAttachment.file_path, 3600);
                    } catch (err) {
                        logger.error(`Failed to get video from MinIO: ${err}`);
                    }
                }
            }

            // Get attachments with presigned URLs from MinIO
            const attachmentsWithUrls: {
                id: number;
                lesson_id: number;
                file_name: string;
                file_path: string;
                file_size: number;
                mime_type: string;
                sort_order: number;
                created_at: Date;
                presigned_url: string | null;
            }[] = [];

            try {
                const attachmentsPrefix = getAttachmentsFolder(course_id, lesson_id);
                const attachmentFiles = await listObjects(attachmentsPrefix);

                for (const file of attachmentFiles) {
                    let presigned_url: string | null = null;
                    try {
                        presigned_url = await getPresignedUrl(file.name, 3600);
                    } catch (err) {
                        logger.error(`Failed to generate presigned URL for ${file.name}: ${err}`);
                    }

                    // Extract filename from path
                    const fileName = file.name.split('/').pop() || file.name;
                    // Guess mime type from extension
                    const ext = fileName.split('.').pop()?.toLowerCase() || '';
                    const mimeTypes: { [key: string]: string } = {
                        'pdf': 'application/pdf',
                        'doc': 'application/msword',
                        'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
                        'ppt': 'application/vnd.ms-powerpoint',
                        'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
                        'xls': 'application/vnd.ms-excel',
                        'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
                        'png': 'image/png',
                        'jpg': 'image/jpeg',
                        'jpeg': 'image/jpeg',
                        'gif': 'image/gif',
                        'mp4': 'video/mp4',
                        'zip': 'application/zip',
                    };
                    const mime_type = mimeTypes[ext] || 'application/octet-stream';

                    // Find matching attachment from database or create entry
                    const dbAttachment = lesson.attachments?.find(a => a.file_path === file.name);
                    attachmentsWithUrls.push({
                        id: dbAttachment?.id || 0,
                        lesson_id: lesson_id,
                        file_name: fileName,
                        file_path: file.name,
                        file_size: file.size,
                        mime_type: dbAttachment?.mime_type || mime_type,
                        sort_order: dbAttachment?.sort_order || attachmentsWithUrls.length,
                        created_at: dbAttachment?.created_at || file.lastModified,
                        presigned_url,
                    });
                }
            } catch (err) {
                logger.error(`Failed to list attachments from MinIO: ${err}`);
            }

            // Build response with MinIO URLs
            const lessonData = {
                ...lesson,
                video_url,
                attachments: attachmentsWithUrls.length > 0 ? attachmentsWithUrls : lesson.attachments,
            };

            return { code: 200, message: 'Lesson fetched successfully', data: lessonData as LessonData };
        } catch (error) {
            logger.error(`Error fetching lesson: ${error}`);
            throw error;
        }
    }

    async updateLesson(request: UpdateLessonRequest): Promise<UpdateLessonResponse> {
        try {
            const { token, course_id, lesson_id, data } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);
            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) {
                throw new UnauthorizedError('Invalid token');
            }
            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) {
                throw new ForbiddenError('You are not permitted to update lesson');
            }
            const lesson = await prisma.lesson.update({ where: { id: lesson_id }, data });
            return { code: 200, message: 'Lesson updated successfully', data: lesson as LessonData };
        } catch (error) {
            logger.error(`Error updating lesson: ${error}`);
            throw error;
        }
    }

    /**
     * เรียงลำดับบทเรียนใหม่
     * Reorder lessons within a chapter
     */
    async reorderLessons(request: ReorderLessonsRequest): Promise<ReorderLessonsResponse> {
        try {
            const { token, course_id, chapter_id, lesson_id, sort_order: newSortOrder } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to reorder lessons');

            // Verify chapter exists and belongs to the course
            const chapter = await prisma.chapter.findUnique({ where: { id: chapter_id } });
            if (!chapter || chapter.course_id !== course_id) {
                throw new NotFoundError('Chapter not found');
            }

            // Get current lesson to find its current sort_order
            const currentLesson = await prisma.lesson.findUnique({ where: { id: lesson_id } });
            if (!currentLesson) {
                throw new NotFoundError('Lesson not found');
            }
            if (currentLesson.chapter_id !== chapter_id) {
                throw new NotFoundError('Lesson not found in this chapter');
            }

            // First, normalize sort_order to fix any gaps or duplicates
            await this.normalizeLessonSortOrder(chapter_id);

            // Re-fetch the lesson to get updated sort_order after normalization
            const normalizedLesson = await prisma.lesson.findUnique({ where: { id: lesson_id } });
            if (!normalizedLesson) throw new NotFoundError('Lesson not found');

            const oldSortOrder = normalizedLesson.sort_order;

            // Get total lesson count to validate sort_order (1-based)
            const lessonCount = await prisma.lesson.count({ where: { chapter_id } });
            const validNewSortOrder = Math.max(1, Math.min(newSortOrder, lessonCount));

            // If same position, no need to reorder
            if (oldSortOrder === validNewSortOrder) {
                const lessons = await prisma.lesson.findMany({
                    where: { chapter_id },
                    orderBy: { sort_order: 'asc' }
                });
                return { code: 200, message: 'Lessons reordered successfully', data: lessons as LessonData[] };
            }

            // Shift other lessons to make room for the insert
            if (oldSortOrder > validNewSortOrder) {
                // Moving up: shift lessons between newSortOrder and oldSortOrder-1 down by 1
                await prisma.lesson.updateMany({
                    where: {
                        chapter_id,
                        sort_order: { gte: validNewSortOrder, lt: oldSortOrder }
                    },
                    data: { sort_order: { increment: 1 } }
                });
            } else {
                // Moving down: shift lessons between oldSortOrder+1 and newSortOrder up by 1
                await prisma.lesson.updateMany({
                    where: {
                        chapter_id,
                        sort_order: { gt: oldSortOrder, lte: validNewSortOrder }
                    },
                    data: { sort_order: { decrement: 1 } }
                });
            }

            // Update the target lesson to the new position
            await prisma.lesson.update({ where: { id: lesson_id }, data: { sort_order: validNewSortOrder } })

            // Fetch all lessons with updated order
            const lessons = await prisma.lesson.findMany({
                where: { chapter_id },
                orderBy: { sort_order: 'asc' }
            });

            return { code: 200, message: 'Lessons reordered successfully', data: lessons as LessonData[] };
        } catch (error) {
            logger.error(`Error reordering lessons: ${error}`);
            throw error;
        }
    }

    /**
     * ลบบทเรียนพร้อมข้อมูลที่เกี่ยวข้องทั้งหมด
     * Delete lesson with all related data (quiz, questions, choices, attachments)
     * ไม่สามารถลบได้ถ้าบทเรียนถูก publish แล้ว
     */
    async deleteLesson(request: DeleteLessonRequest): Promise<DeleteLessonResponse> {
        try {
            const { token, course_id, lesson_id } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to delete this lesson');

            // Fetch lesson with all related data
            const lesson = await prisma.lesson.findUnique({
                where: { id: lesson_id },
                include: {
                    attachments: true,
                    quiz: {
                        include: {
                            questions: {
                                include: { choices: true }
                            }
                        }
                    }
                }
            });

            if (!lesson) throw new NotFoundError('Lesson not found');

            // Delete attachment files from MinIO
            if (lesson.attachments && lesson.attachments.length > 0) {
                for (const attachment of lesson.attachments) {
                    try {
                        await deleteFile(attachment.file_path);
                    } catch (err) {
                        logger.warn(`Failed to delete file from MinIO: ${attachment.file_path}`);
                    }
                }
            }

            // Get chapter_id before deletion for normalization
            const chapterId = lesson.chapter_id;

            // Delete lesson (CASCADE will delete: attachments, quiz, questions, choices)
            // Based on Prisma schema: onDelete: Cascade
            await prisma.lesson.delete({ where: { id: lesson_id } });

            // Normalize sort_order for remaining lessons (fill gaps)
            await this.normalizeLessonSortOrder(chapterId);

            return { code: 200, message: 'Lesson deleted successfully' };
        } catch (error) {
            logger.error(`Error deleting lesson: ${error}`);
            throw error;
        }
    }

    // ============================================
    // Separate Video/Attachment APIs
    // ============================================

    /**
     * อัพโหลดวิดีโอใหม่ให้บทเรียน (ครั้งแรก)
     * Upload video to lesson (first time)
     */
    async uploadVideo(request: UploadVideoInput): Promise<VideoOperationResponse> {
        try {
            const { token, course_id, lesson_id, video } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists and is VIDEO type
            const lesson = await prisma.lesson.findUnique({ where: { id: lesson_id } });
            if (!lesson) throw new NotFoundError('Lesson not found');
            if (lesson.type !== 'VIDEO') throw new ValidationError('Cannot add video to non-VIDEO type lesson');

            // Check if video already exists
            const existingVideo = await prisma.lessonAttachment.findFirst({
                where: { lesson_id, sort_order: 0 }
            });
            if (existingVideo) {
                throw new ValidationError('Video already exists. Use update video API instead.');
            }

            // Upload video to MinIO
            const videoPath = generateFilePath(course_id, lesson_id, 'video', video.originalname);
            await uploadFile(videoPath, video.buffer, video.mimetype);

            // Save video as attachment (sort_order = 0 for video)
            await prisma.lessonAttachment.create({
                data: {
                    lesson_id,
                    file_name: video.originalname,
                    file_size: video.size,
                    mime_type: video.mimetype,
                    file_path: videoPath,
                    sort_order: 0,
                }
            });

            // Get presigned URL
            const video_url = await getPresignedUrl(videoPath, 3600);

            return {
                code: 200,
                message: 'Video uploaded successfully',
                data: {
                    lesson_id,
                    video_url,
                    file_name: video.originalname,
                    file_size: video.size,
                    mime_type: video.mimetype,
                }
            };
        } catch (error) {
            logger.error(`Error uploading video: ${error}`);
            throw error;
        }
    }

    /**
     * อัพเดต (เปลี่ยน) วิดีโอของบทเรียน
     * Update (replace) video in lesson
     */
    async updateVideo(request: UpdateVideoInput): Promise<VideoOperationResponse> {
        try {
            const { token, course_id, lesson_id, video } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists and is VIDEO type
            const lesson = await prisma.lesson.findUnique({ where: { id: lesson_id } });
            if (!lesson) throw new NotFoundError('Lesson not found');
            if (lesson.type !== 'VIDEO') throw new ValidationError('Cannot update video for non-VIDEO type lesson');

            // Find existing video
            const existingVideo = await prisma.lessonAttachment.findFirst({
                where: { lesson_id, sort_order: 0 }
            });

            // Upload new video to MinIO
            const newVideoPath = generateFilePath(course_id, lesson_id, 'video', video.originalname);
            await uploadFile(newVideoPath, video.buffer, video.mimetype);

            if (existingVideo) {
                // Delete old video from MinIO
                try {
                    await deleteFile(existingVideo.file_path);
                } catch (err) {
                    logger.warn(`Failed to delete old video: ${existingVideo.file_path}`);
                }

                // Update lessonAttachment with new video info
                await prisma.lessonAttachment.update({
                    where: { id: existingVideo.id },
                    data: {
                        file_name: video.originalname,
                        file_size: video.size,
                        mime_type: video.mimetype,
                        file_path: newVideoPath,
                    }
                });
            } else {
                // No existing video, create new one
                await prisma.lessonAttachment.create({
                    data: {
                        lesson_id,
                        file_name: video.originalname,
                        file_size: video.size,
                        mime_type: video.mimetype,
                        file_path: newVideoPath,
                        sort_order: 0,
                    }
                });
            }

            // Get presigned URL
            const video_url = await getPresignedUrl(newVideoPath, 3600);

            return {
                code: 200,
                message: 'Video updated successfully',
                data: {
                    lesson_id,
                    video_url,
                    file_name: video.originalname,
                    file_size: video.size,
                    mime_type: video.mimetype,
                }
            };
        } catch (error) {
            logger.error(`Error updating video: ${error}`);
            throw error;
        }
    }

    /**
     * ตั้งค่าวิดีโอ YouTube ให้บทเรียน
     * Set YouTube video for a lesson (replaces existing video if any)
     */
    async setYouTubeVideo(request: SetYouTubeVideoInput): Promise<YouTubeVideoResponse> {
        try {
            const { token, course_id, lesson_id, youtube_video_id, video_title } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists and is VIDEO type
            const lesson = await prisma.lesson.findUnique({ where: { id: lesson_id } });
            if (!lesson) throw new NotFoundError('Lesson not found');
            if (lesson.type !== 'VIDEO') throw new ValidationError('Cannot add video to non-VIDEO type lesson');

            // Find existing video attachment (sort_order = 0)
            const existingVideo = await prisma.lessonAttachment.findFirst({
                where: { lesson_id, sort_order: 0 }
            });

            if (existingVideo) {
                // If existing video is from MinIO (not YouTube), delete it
                if (existingVideo.mime_type !== 'video/youtube') {
                    try {
                        await deleteFile(existingVideo.file_path);
                        logger.info(`Deleted old MinIO video: ${existingVideo.file_path}`);
                    } catch (err) {
                        logger.warn(`Failed to delete old video from MinIO: ${existingVideo.file_path}`);
                    }
                }

                // Update existing attachment to YouTube
                await prisma.lessonAttachment.update({
                    where: { id: existingVideo.id },
                    data: {
                        file_name: video_title,
                        file_path: youtube_video_id,
                        file_size: 0,
                        mime_type: 'video/youtube',
                    }
                });
            } else {
                // Create new YouTube video attachment
                await prisma.lessonAttachment.create({
                    data: {
                        lesson_id,
                        file_name: video_title,
                        file_path: youtube_video_id,
                        file_size: 0,
                        mime_type: 'video/youtube',
                        sort_order: 0,
                    }
                });
            }

            // Build YouTube URL
            const video_url = `https://www.youtube.com/watch?v=${youtube_video_id}`;

            return {
                code: 200,
                message: 'YouTube video set successfully',
                data: {
                    lesson_id,
                    video_url,
                    video_id: youtube_video_id,
                    video_title,
                    mime_type: 'video/youtube',
                }
            };
        } catch (error) {
            logger.error(`Error setting YouTube video: ${error}`);
            throw error;
        }
    }

    /**
     * อัพโหลดไฟล์แนบทีละไฟล์
     * Upload a single attachment to lesson
     */
    async uploadAttachment(request: UploadAttachmentInput): Promise<AttachmentOperationResponse> {
        try {
            const { token, course_id, lesson_id, attachment } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists
            const lesson = await prisma.lesson.findUnique({ where: { id: lesson_id } });
            if (!lesson) throw new NotFoundError('Lesson not found');

            // Get current max sort_order for attachments (excluding video at sort_order 0)
            const maxSortOrder = await prisma.lessonAttachment.aggregate({
                where: { lesson_id, sort_order: { gt: 0 } },
                _max: { sort_order: true }
            });
            const nextSortOrder = (maxSortOrder._max.sort_order ?? 0) + 1;

            // Upload attachment to MinIO
            const attachmentPath = generateFilePath(course_id, lesson_id, 'attachment', attachment.originalname);
            await uploadFile(attachmentPath, attachment.buffer, attachment.mimetype);

            // Save attachment to database
            const newAttachment = await prisma.lessonAttachment.create({
                data: {
                    lesson_id,
                    file_name: attachment.originalname,
                    file_size: attachment.size,
                    mime_type: attachment.mimetype,
                    file_path: attachmentPath,
                    sort_order: nextSortOrder,
                }
            });

            // Get presigned URL
            const presigned_url = await getPresignedUrl(attachmentPath, 3600);

            return {
                code: 200,
                message: 'Attachment uploaded successfully',
                data: {
                    id: newAttachment.id,
                    lesson_id: newAttachment.lesson_id,
                    file_name: newAttachment.file_name,
                    file_path: newAttachment.file_path,
                    file_size: newAttachment.file_size,
                    mime_type: newAttachment.mime_type,
                    sort_order: newAttachment.sort_order,
                    created_at: newAttachment.created_at,
                    presigned_url,
                } as LessonAttachmentData
            };
        } catch (error) {
            logger.error(`Error uploading attachment: ${error}`);
            throw error;
        }
    }

    /**
     * ลบไฟล์แนบทีละไฟล์
     * Delete a single attachment from lesson
     */
    async deleteAttachment(request: DeleteAttachmentInput): Promise<DeleteAttachmentResponse> {
        try {
            const { token, course_id, lesson_id, attachment_id } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists
            const lesson = await prisma.lesson.findUnique({ where: { id: lesson_id } });
            if (!lesson) throw new NotFoundError('Lesson not found');

            // Find attachment
            const attachment = await prisma.lessonAttachment.findUnique({
                where: { id: attachment_id }
            });
            if (!attachment) throw new NotFoundError('Attachment not found');
            if (attachment.lesson_id !== lesson_id) throw new NotFoundError('Attachment not found in this lesson');

            // Don't allow deleting video (sort_order = 0)
            if (attachment.sort_order === 0) {
                throw new ValidationError('Cannot delete video using this API. Use delete video API instead.');
            }

            // Delete file from MinIO
            try {
                await deleteFile(attachment.file_path);
            } catch (err) {
                logger.warn(`Failed to delete file from MinIO: ${attachment.file_path}`);
            }

            // Delete attachment record from database
            await prisma.lessonAttachment.delete({ where: { id: attachment_id } });

            return { code: 200, message: 'Attachment deleted successfully' };
        } catch (error) {
            logger.error(`Error deleting attachment: ${error}`);
            throw error;
        }
    }

    /**
     * เพิ่มคำถามทีละข้อให้ Quiz Lesson
     * Add a single question with choices to an existing QUIZ lesson
     * คะแนนจะถูกคำนวณอัตโนมัติ (100 คะแนน / จำนวนข้อ)
     */
    async addQuestion(request: AddQuestionInput): Promise<AddQuestionResponse> {
        try {
            const { token, course_id, lesson_id, question, explanation, question_type, sort_order, choices } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists and is QUIZ type
            const lesson = await prisma.lesson.findUnique({
                where: { id: lesson_id },
                include: { quiz: true }
            });
            if (!lesson) throw new NotFoundError('Lesson not found');
            if (lesson.type !== 'QUIZ') throw new ValidationError('Cannot add question to non-QUIZ type lesson');
            if (!lesson.quiz) throw new NotFoundError('Quiz not found for this lesson');

            // Get current max sort_order
            const maxSortOrder = await prisma.question.aggregate({
                where: { quiz_id: lesson.quiz.id },
                _max: { sort_order: true }
            });
            const nextSortOrder = sort_order ?? ((maxSortOrder._max.sort_order ?? -1) + 1);

            // Create the question with temporary score (will be recalculated)
            const newQuestion = await prisma.question.create({
                data: {
                    quiz_id: lesson.quiz.id,
                    question: question,
                    explanation: explanation,
                    question_type: question_type,
                    score: 1, // Temporary, will be recalculated
                    sort_order: nextSortOrder,
                }
            });

            // Create choices if provided
            if (choices && choices.length > 0) {
                for (let i = 0; i < choices.length; i++) {
                    const choiceInput = choices[i];
                    await prisma.choice.create({
                        data: {
                            question_id: newQuestion.id,
                            text: choiceInput.text,
                            is_correct: choiceInput.is_correct,
                            sort_order: choiceInput.sort_order ?? i,
                        }
                    });
                }
            }

            // Recalculate scores for all questions (100 points total)
            await this.recalculateQuestionScores(lesson.quiz.id);

            // Fetch complete question with choices (with updated score)
            const completeQuestion = await prisma.question.findUnique({
                where: { id: newQuestion.id },
                include: { choices: { orderBy: { sort_order: 'asc' } } }
            });

            return { code: 200, message: 'Question added successfully', data: completeQuestion as QuizQuestionData };
        } catch (error) {
            logger.error(`Error adding question: ${error}`);
            throw error;
        }
    }

    /**
     * อัปเดตคำถาม
     * Update a question and optionally replace all choices
     * หมายเหตุ: คะแนนจะถูกคำนวณอัตโนมัติ ไม่สามารถเปลี่ยนได้
     */
    async updateQuestion(request: UpdateQuestionInput): Promise<UpdateQuestionResponse> {
        try {
            const { token, course_id, lesson_id, question_id, question, explanation, question_type, sort_order, choices } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists and is QUIZ type
            const lesson = await prisma.lesson.findUnique({
                where: { id: lesson_id },
                include: { quiz: true }
            });
            if (!lesson) throw new NotFoundError('Lesson not found');
            if (lesson.type !== 'QUIZ') throw new ValidationError('Cannot update question in non-QUIZ type lesson');
            if (!lesson.quiz) throw new NotFoundError('Quiz not found for this lesson');

            // Verify question exists and belongs to this quiz
            const existingQuestion = await prisma.question.findUnique({ where: { id: question_id } });
            if (!existingQuestion || existingQuestion.quiz_id !== lesson.quiz.id) {
                throw new NotFoundError('Question not found in this quiz');
            }

            // Update the question (score is NOT updated - it's auto-calculated)
            await prisma.question.update({
                where: { id: question_id },
                data: {
                    question: (question ?? existingQuestion.question) as Prisma.InputJsonValue,
                    explanation: (explanation ?? existingQuestion.explanation) as Prisma.InputJsonValue,
                    question_type: question_type ?? existingQuestion.question_type,
                    sort_order: sort_order ?? existingQuestion.sort_order,
                }
            });

            // If choices provided, replace all choices
            if (choices && choices.length > 0) {
                // Delete existing choices
                await prisma.choice.deleteMany({ where: { question_id: question_id } });

                // Create new choices
                for (let i = 0; i < choices.length; i++) {
                    const choiceInput = choices[i];
                    await prisma.choice.create({
                        data: {
                            question_id: question_id,
                            text: choiceInput.text,
                            is_correct: choiceInput.is_correct,
                            sort_order: choiceInput.sort_order ?? i,
                        }
                    });
                }
            }

            // Fetch complete question with choices
            const completeQuestion = await prisma.question.findUnique({
                where: { id: question_id },
                include: { choices: { orderBy: { sort_order: 'asc' } } }
            });

            return { code: 200, message: 'Question updated successfully', data: completeQuestion as QuizQuestionData };
        } catch (error) {
            logger.error(`Error updating question: ${error}`);
            throw error;
        }
    }

    async reorderQuestion(request: ReorderQuestionInput): Promise<ReorderQuestionResponse> {
        try {
            const { token, course_id, lesson_id, question_id, sort_order } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists and is QUIZ type
            const lesson = await prisma.lesson.findUnique({
                where: { id: lesson_id },
                include: { quiz: true }
            });
            if (!lesson) throw new NotFoundError('Lesson not found');
            if (lesson.type !== 'QUIZ') throw new ValidationError('Cannot reorder question in non-QUIZ type lesson');
            if (!lesson.quiz) throw new NotFoundError('Quiz not found for this lesson');

            // Verify question exists and belongs to this quiz
            const existingQuestion = await prisma.question.findUnique({ where: { id: question_id } });
            if (!existingQuestion || existingQuestion.quiz_id !== lesson.quiz.id) {
                throw new NotFoundError('Question not found in this quiz');
            }

            const quizId = lesson.quiz.id;

            // First, normalize sort_order to fix any gaps or duplicates
            await this.normalizeQuestionSortOrder(quizId);

            // Re-fetch the question to get updated sort_order after normalization
            const normalizedQuestion = await prisma.question.findUnique({ where: { id: question_id } });
            if (!normalizedQuestion) throw new NotFoundError('Question not found');

            const oldSortOrder = normalizedQuestion.sort_order;

            // Get total question count to validate sort_order (1-based)
            const questionCount = await prisma.question.count({ where: { quiz_id: quizId } });
            const newSortOrder = Math.max(1, Math.min(sort_order, questionCount));

            // If same position, no need to reorder
            if (oldSortOrder === newSortOrder) {
                const questions = await prisma.question.findMany({
                    where: { quiz_id: quizId },
                    orderBy: { sort_order: 'asc' },
                    include: { choices: { orderBy: { sort_order: 'asc' } } }
                });
                return { code: 200, message: 'Question reordered successfully', data: questions as QuizQuestionData[] };
            }

            // Shift other questions to make room for the insert
            if (oldSortOrder > newSortOrder) {
                // Moving up: shift questions between newSortOrder and oldSortOrder-1 down by 1
                await prisma.question.updateMany({
                    where: {
                        quiz_id: quizId,
                        sort_order: { gte: newSortOrder, lt: oldSortOrder }
                    },
                    data: { sort_order: { increment: 1 } }
                });
            } else {
                // Moving down: shift questions between oldSortOrder+1 and newSortOrder up by 1
                await prisma.question.updateMany({
                    where: {
                        quiz_id: quizId,
                        sort_order: { gt: oldSortOrder, lte: newSortOrder }
                    },
                    data: { sort_order: { decrement: 1 } }
                });
            }

            // Update the question's sort order
            await prisma.question.update({
                where: { id: question_id },
                data: { sort_order: newSortOrder }
            });

            // Fetch all questions with updated order
            const questions = await prisma.question.findMany({
                where: { quiz_id: quizId },
                orderBy: { sort_order: 'asc' },
                include: { choices: { orderBy: { sort_order: 'asc' } } }
            });

            return { code: 200, message: 'Question reordered successfully', data: questions as QuizQuestionData[] };
        } catch (error) {
            logger.error(`Error reordering question: ${error}`);
            throw error;
        }
    }

    /**
     * ลบคำถาม
     * Delete a question and all its choices
     * คะแนนจะถูกคำนวณใหม่หลังจากลบ
     */
    async deleteQuestion(request: DeleteQuestionInput): Promise<DeleteQuestionResponse> {
        try {
            const { token, course_id, lesson_id, question_id } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists and is QUIZ type
            const lesson = await prisma.lesson.findUnique({
                where: { id: lesson_id },
                include: { quiz: true }
            });
            if (!lesson) throw new NotFoundError('Lesson not found');
            if (lesson.type !== 'QUIZ') throw new ValidationError('Cannot delete question from non-QUIZ type lesson');
            if (!lesson.quiz) throw new NotFoundError('Quiz not found for this lesson');

            // Verify question exists and belongs to this quiz
            const existingQuestion = await prisma.question.findUnique({ where: { id: question_id } });
            if (!existingQuestion || existingQuestion.quiz_id !== lesson.quiz.id) {
                throw new NotFoundError('Question not found in this quiz');
            }

            // Delete the question (CASCADE will delete choices)
            await prisma.question.delete({ where: { id: question_id } });

            // Normalize sort_order for remaining questions (fill gaps)
            await this.normalizeQuestionSortOrder(lesson.quiz.id);

            // Recalculate scores for remaining questions
            await this.recalculateQuestionScores(lesson.quiz.id);

            return { code: 200, message: 'Question deleted successfully' };
        } catch (error) {
            logger.error(`Error deleting question: ${error}`);
            throw error;
        }
    }

    /**
     * คำนวณคะแนนใหม่สำหรับทุกคำถามใน Quiz
     * Recalculate scores for all questions in a quiz (100 points total)
     * Distributes points as integers, handling remainders to ensure exact 100 total.
     * 
     * @param quizId Quiz ID to recalculate
     */
    private async recalculateQuestionScores(quizId: number): Promise<void> {
        // Get all questions in this quiz
        const questions = await prisma.question.findMany({
            where: { quiz_id: quizId },
            orderBy: { sort_order: 'asc' },
            select: { id: true }
        });

        const questionCount = questions.length;
        if (questionCount === 0) return;

        // Calculate base score and remainder
        const baseScore = Math.floor(100 / questionCount);
        const remainder = 100 % questionCount;

        // Prepare updates
        const updates: any[] = [];

        for (let i = 0; i < questionCount; i++) {
            // Distribute remainder to the first 'remainder' questions
            const score = i < remainder ? baseScore + 1 : baseScore;

            updates.push(
                prisma.question.update({
                    where: { id: questions[i].id },
                    data: { score: score }
                })
            );
        }

        // Execute all updates in a transaction
        if (updates.length > 0) {
            await prisma.$transaction(updates);
        }

        logger.info(`Recalculated quiz ${quizId}: ${questionCount} questions. Base: ${baseScore}, Remainder: ${remainder}`);
    }

    /**
     * Normalize sort_order for all questions in a quiz
     * Ensures sort_order is sequential starting from 0 with no gaps or duplicates
     * 
     * @param quizId Quiz ID to normalize
     */
    private async normalizeQuestionSortOrder(quizId: number): Promise<void> {
        // Get all questions ordered by current sort_order
        const questions = await prisma.question.findMany({
            where: { quiz_id: quizId },
            orderBy: { sort_order: 'asc' },
            select: { id: true, sort_order: true }
        });

        if (questions.length === 0) return;

        // Update each question with sequential sort_order starting from 1
        const updates = questions.map((question, index) =>
            prisma.question.update({
                where: { id: question.id },
                data: { sort_order: index + 1 }
            })
        );

        await prisma.$transaction(updates);
        logger.info(`Normalized sort_order for quiz ${quizId}: ${questions.length} questions`);
    }

    /**
     * Normalize sort_order for all lessons in a chapter
     * Ensures sort_order is sequential starting from 1 with no gaps or duplicates
     * 
     * @param chapterId Chapter ID to normalize
     */
    private async normalizeLessonSortOrder(chapterId: number): Promise<void> {
        // Get all lessons ordered by current sort_order
        const lessons = await prisma.lesson.findMany({
            where: { chapter_id: chapterId },
            orderBy: { sort_order: 'asc' },
            select: { id: true, sort_order: true }
        });

        if (lessons.length === 0) return;

        // Update each lesson with sequential sort_order starting from 1
        const updates = lessons.map((lesson, index) =>
            prisma.lesson.update({
                where: { id: lesson.id },
                data: { sort_order: index + 1 }
            })
        );

        await prisma.$transaction(updates);
        logger.info(`Normalized sort_order for chapter ${chapterId}: ${lessons.length} lessons`);
    }

    /**
     * Normalize sort_order for all chapters in a course
     * Ensures sort_order is sequential starting from 1 with no gaps or duplicates
     * 
     * @param courseId Course ID to normalize
     */
    private async normalizeChapterSortOrder(courseId: number): Promise<void> {
        // Get all chapters ordered by current sort_order
        const chapters = await prisma.chapter.findMany({
            where: { course_id: courseId },
            orderBy: { sort_order: 'asc' },
            select: { id: true, sort_order: true }
        });

        if (chapters.length === 0) return;

        // Update each chapter with sequential sort_order starting from 1
        const updates = chapters.map((chapter, index) =>
            prisma.chapter.update({
                where: { id: chapter.id },
                data: { sort_order: index + 1 }
            })
        );

        await prisma.$transaction(updates);
        logger.info(`Normalized sort_order for course ${courseId}: ${chapters.length} chapters`);
    }

    /**
     * อัพเดตการตั้งค่า Quiz
     * Update quiz settings (title, passing_score, time_limit, etc.)
     */
    async updateQuiz(request: UpdateQuizInput): Promise<UpdateQuizResponse> {
        try {
            const { token, course_id, lesson_id, title, description, passing_score, time_limit, shuffle_questions, shuffle_choices, show_answers_after_completion, is_skippable } = request;
            const decodedToken = jwt.verify(token, config.jwt.secret) as { id: number };
            await CoursesInstructorService.validateCourseStatus(course_id);

            const user = await prisma.user.findUnique({ where: { id: decodedToken.id } });
            if (!user) throw new UnauthorizedError('Invalid token');

            const courseInstructor = await CoursesInstructorService.validateCourseInstructor(token, course_id);
            if (!courseInstructor) throw new ForbiddenError('You are not permitted to modify this lesson');

            // Verify lesson exists and is QUIZ type
            const lesson = await prisma.lesson.findUnique({
                where: { id: lesson_id },
                include: { quiz: { include: { questions: { include: { choices: { orderBy: { sort_order: 'asc' } } }, orderBy: { sort_order: 'asc' } } } } }
            });
            if (!lesson) throw new NotFoundError('Lesson not found');
            if (lesson.type !== 'QUIZ') throw new ValidationError('This lesson is not a quiz');
            if (!lesson.quiz) throw new NotFoundError('Quiz not found for this lesson');

            // Build update data (only include provided fields)
            const updateData: any = {
                updated_by: user.id,
            };
            if (title !== undefined) updateData.title = title;
            if (description !== undefined) updateData.description = description;
            if (passing_score !== undefined) updateData.passing_score = passing_score;
            if (time_limit !== undefined) updateData.time_limit = time_limit;
            if (shuffle_questions !== undefined) updateData.shuffle_questions = shuffle_questions;
            if (shuffle_choices !== undefined) updateData.shuffle_choices = shuffle_choices;
            if (show_answers_after_completion !== undefined) updateData.show_answers_after_completion = show_answers_after_completion;
            if (is_skippable !== undefined) updateData.is_skippable = is_skippable;

            // Update the quiz
            const updatedQuiz = await prisma.quiz.update({
                where: { id: lesson.quiz.id },
                data: updateData,
                include: { questions: { include: { choices: { orderBy: { sort_order: 'asc' } } }, orderBy: { sort_order: 'asc' } } }
            });

            return { code: 200, message: 'Quiz updated successfully', data: updatedQuiz as unknown as QuizData };
        } catch (error) {
            logger.error(`Error updating quiz: ${error}`);
            throw error;
        }
    }

}