import type { Request, Response } from "express";
import asyncHandler from "express-async-handler";
import { prisma } from "../config/db.ts";
import { getHost } from "../utils/utils.ts";
import { toIST } from "../utils/time.ts";

// Create PracticeTest
export const createPracticeTest = asyncHandler(async (req: Request, res: Response) => {
    const {
        exam,
        language,
        title,
        questions,
        duration,
        marks,
        difficulty,
        publish
    } = req.body;

    const user = (req as any).user;
    const userId = user.id;

    if (!title) {
        res.status(400).json({ success: false, message: "Title is required" });
        return;
    }

    if (!exam) {
        res.status(400).json({ success: false, message: "Exam is required" });
        return;
    }

    const existTest = await prisma.practiceTest.findFirst({ where: { title: { equals: title }, examId: exam, institutionId: user.institutionId } });
    if (existTest) {
        res.status(400).json({ success: false, message: "Practice test title already exists" });
        return;
    }

    if (!marks) {
        res.status(400).json({ success: false, message: "Marks is required" });
        return;
    }
    if (!duration) {
        res.status(400).json({ success: false, message: "Duration is required" });
        return;
    }
    if (!questions?.length) {
        res.status(400).json({ success: false, message: "Questions are required" });
        return;
    }

    const newTest = await prisma.practiceTest.create({
        data: {
            createdById: userId,
            examId: exam,
            language: language || "English",
            title,
            duration,
            marks: parseFloat(marks),
            difficulty: difficulty || "Medium",
            publish: publish === true || publish === "true",
            testId: req.body.testId || null,
            institutionId: user.institutionId
        }
    });

    res.status(201).json({
        success: true,
        message: "PracticeTest created successfully",
        data: {
            ...newTest,
            createdAt: toIST(newTest.createdAt),
            updatedAt: toIST(newTest.updatedAt),
        },
    });
});

// List PracticeTest
export const listPracticeTest = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;

    const { search, exam, difficulty, publish, language, page = "1", limit = "10" } = req.query;

    const pageNum = parseInt(page as string);
    const limitNum = parseInt(limit as string);
    const skip = (pageNum - 1) * limitNum;

    const where: any = {
        institutionId: user.institutionId
    };

    if (search) {
        where.title = { contains: search as string };
    }

    if (language) {
        where.language = language as string;
    }

    if (exam) {
        const examIds = Array.isArray(exam) ? exam : [exam];
        where.examId = { in: examIds };
    }

    if (difficulty) {
        where.difficulty = difficulty;
    }

    if (publish !== undefined) {
        where.publish = String(publish) === "true";
    }

    const [total, tests] = await Promise.all([
        prisma.practiceTest.count({ where }),
        prisma.practiceTest.findMany({
            where,
            include: {
                exam: { select: { examName: true } },
                test: { 
                    include: { 
                        testQuestions: true,
                        referenceInstitution: { select: { user: { select: { institutionName: true, firstName: true } } } }
                    } 
                },
                questions: { select: { id: true } },
                _count: { select: { examResults: true } }
            },
            orderBy: { title: 'asc' },
            skip,
            take: limitNum,
        }),
    ]);

    const updatedTests = await Promise.all(tests.map(async (test) => {
        let questionIds = [
            ...(test.test?.testQuestions.map((tq) => tq.questionId) || []),
            ...(test.questions?.map((q) => q.id) || [])
        ];

        if (test.test?.referenceSourceId) {
            const sourceTest = await prisma.test.findUnique({
                where: { id: test.test.referenceSourceId },
                include: { testQuestions: true }
            });
            if (sourceTest) {
                questionIds = sourceTest.testQuestions.map(tq => tq.questionId);
            }
        }

        return {
            id: test.id,
            title: test.title,
            examId: test.examId,
            exam: test.exam?.examName,
            language: test.language,
            duration: test.duration,
            marks: test.marks,
            difficulty: test.difficulty,
            publish: test.publish,
            questions: Array.from(new Set(questionIds)),
            tests: test.test ? [test.test.id] : [],
            testId: test.testId,
            referenceSourceId: test.test?.referenceSourceId,
            referenceInstitution: test.test?.referenceInstitution,
            hasResults: (test._count?.examResults ?? 0) > 0,
            createdAt: toIST(test.createdAt),
            updatedAt: toIST(test.updatedAt),
        };
    }));

    res.status(200).json({
        message: "Successfully fetched practice tests",
        data: updatedTests,
        meta: {
            total,
            page: pageNum,
            limit: limitNum,
            totalPages: Math.ceil(total / limitNum),
        },
    });
});

// PracticeTest By Id
export const practiceTestById = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;
    const user = (req as any).user;

    const test = await prisma.practiceTest.findFirst({
        where: { id, institutionId: user.institutionId },
        include: {
            test: {
                include: {
                    testQuestions: {
                        include: {
                            question: {
                                select: {
                                    id: true,
                                    questionText: true,
                                    options: true,
                                    correctAnswer: true,
                                    topic: true,
                                    language: true,
                                    marks: true,
                                    difficulty: true,
                                }
                            }
                        }
                    }
                }
            },
            createdBy: { select: { firstName: true, lastName: true } }
        }
    });

    if (!test) {
        res.status(404).json({ message: "Practice test not found" });
        return;
    }

    const updatedTest = {
        ...test,
        createdAt: toIST(test.createdAt),
        updatedAt: toIST(test.updatedAt),
        questions: test.test?.testQuestions.map((tq: any) => ({
            ...tq.question,
        })) || [],
        totalQuestions: test.test?.testQuestions.length || 0,
        createdBy: test.createdBy
            ? `${test.createdBy.firstName} ${test.createdBy.lastName}`
            : "Unknown",
    };

    res.status(200).json({
        message: "Successfully fetched practice test",
        data: updatedTest,
    });
});

// Update PracticeTest
export const updatePracticeTest = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;
    const user = (req as any).user;
    const { questions, testId, ...updates } = req.body;

    const existing = await prisma.practiceTest.findFirst({
        where: { id, institutionId: user.institutionId }
    });

    if (!existing) {
        res.status(404).json({ message: "Practice test not found" });
        return;
    }

    const allowedFields = ['title', 'language', 'duration', 'marks', 'difficulty', 'publish'];
    const data: any = {};
    for (const field of allowedFields) {
        if (updates[field] !== undefined) {
            data[field] = updates[field];
        }
    }

    if (updates.exam) {
        data.examId = updates.exam;
    }

 

    if (testId) {
        data.testId = testId;
    }
    if (data.marks) data.marks = parseFloat(data.marks);
    if (data.publish !== undefined) data.publish = data.publish === "true" || data.publish === true;

    const resolvedTestId = testId || existing.testId;
    if (resolvedTestId) {
        const questionCount = await prisma.testQuestion.count({ where: { testId: resolvedTestId } });
        data.marks = questionCount;
    }

    const updatedTest = await prisma.practiceTest.update({
        where: { id },
        data: data
    });

    res.status(200).json({
        message: "Practice Test updated successfully",
        data: {
            ...updatedTest,
            createdAt: toIST(updatedTest.createdAt),
            updatedAt: toIST(updatedTest.updatedAt),
        },
    });
});

// Delete PracticeTest
export const deletePracticeTest = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;
    const user = (req as any).user;

    const existing = await prisma.practiceTest.findFirst({
        where: { id, institutionId: user.institutionId }
    });

    if (!existing) {
        res.status(404).json({ message: "Practice test not found" });
        return;
    }

    const hasResults = await prisma.examResult.findFirst({
        where: { practiceTestId: id }
    });

    if (hasResults) {
        res.status(400).json({ message: "Cannot unmap this test because students have already taken it." });
        return;
    }

    await prisma.practiceTest.delete({
        where: { id }
    });

    res.status(200).json({
        message: "PracticeTest deleted successfully",
        deletedId: id,
    });
});

// Student PracticeTest List
export const studentPracticeTestList = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const userId = user.id;
    const studentInstitutionId = user.studentInstitutionId;
    const institutionId = user.institutionId;

    const { search, attempted, exam, difficulty, language, subjectId, page = "1", limit = "10" } = req.query;

    const fetchAll = limit === 'all';

    const pageNum = parseInt(page as string);
    const limitNum = parseInt(limit as string);

    const safePageNum = isNaN(pageNum) ? 1 : pageNum;
    const safeLimitNum = isNaN(limitNum) ? 10 : limitNum;
    const skip = (safePageNum - 1) * safeLimitNum;

    const studentInstitution = await prisma.studentInstitution.findUnique({
        where: { id: studentInstitutionId, deletedAt: null },
        include: {
            examResults: {
                select: {
                    practiceTestId: true
                }
            },
            subscriptions: { where: { isCurrent: true } },
            exam: true,
            student: { include: { user: { select: { details: true } } } }
        }
    });

    if (!studentInstitution || !studentInstitution.institutionId) {
        res.status(404).json({ message: "Student record not found" });
        return;
    }

    // If student subscription has expired
    // if (studentInstitution.subscriptions[0] && studentInstitution.subscriptions[0].status === "EXPIRED") {
    //     res.status(403).json({ message: "Currently you don't have active plan" });
    //     return;
    // }

    const attemptedTestIds = new Set(studentInstitution.examResults.map(r => r.practiceTestId).filter(Boolean));

    const where: any = {
        publish: true,
        institutionId: institutionId,
    };

    if (search) {
        where.title = { contains: search as string };
    }

    if (exam) {
        where.examId = exam as string;
    } else if (studentInstitution.examsId) {
        where.examId = studentInstitution.examsId;
    }
    const motherTongue = studentInstitution.student?.language || studentInstitution.language || "";
    const mediumOfExam = studentInstitution.student?.language ? (studentInstitution.language || "English") : "English";
    const filterLanguage = language || mediumOfExam;

    // Language
    if (filterLanguage || motherTongue) {
        where.AND = where.AND || [];
        const languageOr: any[] = [];
        
        if (motherTongue) {
            languageOr.push(
                { test: { subject: { language: motherTongue as string } } },
                { questions: { some: { subject: { language: motherTongue as string } } } }
            );
        }
        
        if (filterLanguage) {
            languageOr.push(
                { test: { subject: { language: null } }, language: filterLanguage as string },
                { testId: null, questions: { some: { subject: { language: null } } }, language: filterLanguage as string },
                { testId: null, questions: { none: {} }, language: filterLanguage as string }
            );
        }
        
        if (languageOr.length > 0) {
            where.AND.push({ OR: languageOr });
        }
    }

    if (difficulty) {
        where.difficulty = difficulty;
    }

    if (subjectId) {
        where.test = {
            subjectId: subjectId as string
        };
    }
    const baseWhere = { ...where };

    if (attempted === "true") {
        where.id = { in: Array.from(attemptedTestIds) as string[] };
    } else if (attempted === "false") {
        where.id = { notIn: Array.from(attemptedTestIds) as string[] };
    }

    const [allCount, attemptedCount, tests] = await Promise.all([
        prisma.practiceTest.count({ where: baseWhere }),
        prisma.practiceTest.count({
            where: { ...baseWhere, id: { in: Array.from(attemptedTestIds) as string[] } }
        }),
        prisma.practiceTest.findMany({
            where,
            include: {
                exam: { select: { examName: true } },
                test: {
                    select: {
                        subjectId: true,
                        subject: { select: { subjectName: true } },
                        referenceSource: { select: { _count: { select: { testQuestions: true } } } },
                        _count: { select: { testQuestions: true } }
                    }
                }
            },
            orderBy: { title: 'asc' },
            ...(!fetchAll && {
                skip,
                take: safeLimitNum,
            }),
        }),
    ]);

    const yetToAttemptCount = allCount - attemptedCount;

    let total = allCount;
    if (attempted === "true") total = attemptedCount;
    else if (attempted === "false") total = yetToAttemptCount;

    res.status(200).json({
        message: tests.length > 0 ? "Successfully fetched practice tests" : "We will update soon.",
        data: tests.map(test => {
            const totalQs = (test as any).test?.referenceSource?._count?.testQuestions ?? test.test?._count.testQuestions ?? 0;
            return {
                id: test.id,
                exam: test.exam?.examName,
                language: test.language,
                title: test.title,
                duration: test.duration,
                difficulty: test.difficulty,
                subjectId: test.test?.subjectId || null,
                subject: test.test?.subject?.subjectName || null,
                marks: test.marks,
                totalQuestions: totalQs,
                markPerQuestion: test.marks > 0 && totalQs > 0 ? (test.marks / totalQs) : 1,
                attempted: attemptedTestIds.has(test.id),
            };
        }),
        meta: {
            total,
            page: fetchAll ? 1 : safePageNum,
            limit: fetchAll ? total : safeLimitNum,
            totalPages: fetchAll ? 1 : Math.ceil(total / safeLimitNum),
            counts: {
                all: allCount,
                attempted: attemptedCount,
                yetToAttempt: yetToAttemptCount
            }
        },
    });
});

// Get Practice Test by ID for taking the test
export const getTestById = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const studentInstitutionId = user.studentInstitutionId

    const testId = req.params.id;
    const { page = "1", limit = "10" } = req.query;

    const pageNum = parseInt(page as string);
    const limitNum = parseInt(limit as string);
    const skip = (pageNum - 1) * limitNum;

    // Student specific: find their institution/uploader
    const studentInstitution = await prisma.studentInstitution.findUnique({
        where: { id: studentInstitutionId },
        include: {
            subscriptions: { where: { isCurrent: true } },
            student: { include: { user: { select: { details: true } } } }
        }
    });

    if (!studentInstitution) {
        res.status(404).json({ message: "Student record not found" });
        return;
    }

    // If student subscription has expired
    // if (studentInstitution.subscriptions[0] && studentInstitution.subscriptions[0].status === "EXPIRED") {
    //     res.status(403).json({ message: "Currently you don't have active plan" });
    //     return;
    // }

    const practiceTest = await prisma.practiceTest.findFirst({
        where: { id: testId, institutionId: user.institutionId },
        include: {
            test: {
                include: {
                    testQuestions: {
                        include: {
                            question: {
                                select: {
                                    id: true,
                                    questionText: true,
                                    questionImage: true,
                                    options: true,
                                }
                            }
                        },
                        orderBy: {
                            createdAt: "asc"
                        }
                    }
                }
            }
        }
    });

    if (!practiceTest) {
        res.status(404).json({ message: "Practice Test not found" });
        return;
    }

    let mappedQuestions = practiceTest.test?.testQuestions.map((tq: any) => tq.question) || [];

    if (practiceTest.test?.referenceSourceId) {
        const sourceTest = await prisma.test.findUnique({
            where: { id: practiceTest.test.referenceSourceId },
            include: {
                testQuestions: {
                    include: {
                        question: {
                            select: {
                                id: true,
                                questionText: true,
                                questionImage: true,
                                options: true,
                            }
                        }
                    },
                    orderBy: {
                        createdAt: "asc"
                    }
                }
            }
        });
        if (sourceTest) {
            mappedQuestions = sourceTest.testQuestions.map((tq: any) => tq.question);
        }
    }
    const totalQuestions = mappedQuestions.length;
    const paginatedQuestions = mappedQuestions.slice(skip, skip + limitNum);

    const formattedExam = {
        testid: practiceTest.id,
        title: practiceTest.title,
        duration: practiceTest.duration,
        marks: practiceTest.marks,
        difficulty: practiceTest.difficulty,
        questions: paginatedQuestions.map((q: any) => ({
            id: q.id,
            questionText: q.questionText,
            questionImage: q.questionImage ? `${getHost()}${q.questionImage}` : "",
            options: (q.options as any[]).map((opt: any) => ({
                option: String(opt?.option) || "",
                optionImage: opt.optionImage ? `${getHost()}${opt.optionImage}` : "",
            })),
        })),
    };

    res.status(200).json({
        message: "PracticeTest fetched successfully",
        data: formattedExam,
        meta: {
            total: totalQuestions,
            page: pageNum,
            limit: limitNum,
            totalPages: Math.ceil(totalQuestions / limitNum)
        }
    });
});

// get practice test questions (for admin view)
export const getPracticeTestQuestions = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;
    const { page = "1", limit = "10", search, examId } = req.query;
    const user = (req as any).user;

    const pageNum = parseInt(page as string);
    const limitNum = parseInt(limit as string);
    const skip = (pageNum - 1) * limitNum;

    const test = await prisma.practiceTest.findFirst({
        where: { id, institutionId: user.institutionId },
        select: { id: true, exam: { select: { examName: true } } }
    });

    if (!test) {
        res.status(404).json({ message: "Practice test not found" });
        return;
    }

    const practiceTestObj = await prisma.practiceTest.findUnique({
        where: { id },
        select: { testId: true, test: { select: { referenceSourceId: true } } }
    });

    const where: any = {
        OR: [
            { practiceTests: { some: { id } } }
        ]
    };

    if (practiceTestObj?.testId) {
        where.OR.push({ testQuestions: { some: { testId: practiceTestObj.testId } } });
        
        if (practiceTestObj.test?.referenceSourceId) {
            where.OR.push({ testQuestions: { some: { testId: practiceTestObj.test.referenceSourceId } } });
        }
    }

    if (search) {
        where.questionText = { contains: search as string };
    }

    if (examId) {
        where.exams = { some: { id: examId as string } };
    }

    const [total, questions] = await Promise.all([
        prisma.questionBank.count({ where }),
        prisma.questionBank.findMany({
            where,
            include: {
                 subject: { select: { subjectName: true } }
            },
            orderBy: { createdAt: 'desc' },
            skip,
            take: limitNum,
        })
    ]);

    const formattedQuestions = questions.map((q: any) => ({
        ...q,
        createdAt: toIST(q.createdAt),
        updatedAt: toIST(q.updatedAt),
        exam: q.exams?.[0]?.examName || "",
        subject: q.subject?.subjectName || "",
        questionImage: q.questionImage ? `${getHost()}${q.questionImage}` : "",
        options: (q.options as any[]).map((option: any) => ({
            ...option,
            optionImage: option.optionImage ? `${getHost()}${option.optionImage}` : "",
        })),
        explanationImage: q.explanationImage ? `${getHost()}${q.explanationImage}` : "",
    }));

    res.status(200).json({
        message: "Successfully fetched practice test questions",
        data: {
            testId: test.id,
            exam: test.exam?.examName,
        },
        questions: formattedQuestions,
        meta: {
            page: pageNum,
            limit: limitNum,
            total,
            totalPages: Math.ceil(total / limitNum),
        },
    });
});

// List all subject for student (mobile)
export const getStudentSubjects = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const institutionId = user.institutionId;
    const search = (req.query.search as string)?.trim();
    
    const page = Math.max(parseInt(req.query.page as string) || 1, 1);
    const limit = Math.max(parseInt(req.query.limit as string) || 10, 1);
    const skip = (page - 1) * limit;

    if (!institutionId) {
        res.status(400).json({ message: "Institution ID is missing" });
        return;
    }

    const studentInstitution = await prisma.studentInstitution.findUnique({
        where: { id: user.studentInstitutionId },
        include: { student: { include: { user: { select: { details: true } } } } }
    });

    if (!studentInstitution) {
        res.status(404).json({ message: "Student record not found" });
        return;
    }

    const motherTongue = studentInstitution.student?.language || studentInstitution.language;
    const mediumOfExam = studentInstitution.student?.language ? studentInstitution.language : ((studentInstitution.student?.user?.details as any)?.mediumOfExam || "English");

    const practiceTestFilter: any = {
        institutionId,
        publish: true,
    };

    if (studentInstitution.examsId) {
        practiceTestFilter.examId = studentInstitution.examsId;
    }

    const practiceTestFilterMedium: any = { ...practiceTestFilter, language: mediumOfExam };
    const practiceTestFilterMotherTongue: any = { ...practiceTestFilter };

    const where: any = {
        OR: [
            {
                language: motherTongue || undefined,
                OR: [
                    { questions: { some: { practiceTests: { some: practiceTestFilterMotherTongue } } } },
                    { tests: { some: { practiceTests: { some: practiceTestFilterMotherTongue } } } }
                ]
            },
            {
                language: null,
                OR: [
                    { questions: { some: { practiceTests: { some: practiceTestFilterMedium } } } },
                    { tests: { some: { practiceTests: { some: practiceTestFilterMedium } } } }
                ]
            }
        ]
    };

    if (search) {
        where.subjectName = { contains: search };
    }

    const [total, subjects] = await Promise.all([
        prisma.subject.count({ where }),
        prisma.subject.findMany({
            where,
            select: {
                id: true,
                subjectName: true,
                topics: true,
                createdAt: true,
                institutionId: true,
                institution: {
                    select: { user: { select: { institutionName: true } } }
                }
            },
            orderBy: { subjectName: 'asc' },
            skip,
            take: limit,
        }),
    ]);

    const formattedSubjects = subjects.map((s: any) => {
        const isShared = s.institutionId !== institutionId;
        const instName = s.institution?.user?.institutionName;
        return {
            id: s.id,
            subjectName: isShared && instName ? `${s.subjectName}` : s.subjectName,
            topics: s.topics,
            createdAt: s.createdAt,
            isShared,
            sharedInstitutionName: isShared ? instName : null
        };
    });

    res.status(200).json({
        message: "Subjects fetched successfully",
        data: formattedSubjects,
        meta: {
            total,
            page,
            limit,
            totalPages: Math.ceil(total / limit),
        },
    });
});

// Mapping a Test to Exam in Practice Test
export const mapTestToExam = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { testId, examId } = req.body;

    if (!testId || !examId) {
        res.status(400).json({ success: false, message: "Test ID and Exam ID are required" });
        return;
    }

    const test = await prisma.test.findUnique({ 
        where: { id: testId },
        include: { testQuestions: true }
    });
    if (!test) {
        res.status(404).json({ success: false, message: "Test not found" });
        return;
    }

    // Check if this Test is already mapped to this Exam
    const existingMappedTest = await prisma.practiceTest.findFirst({
        where: {
            examId,
            testId: testId
        }
    });

    if (existingMappedTest) {
        res.status(400).json({ success: false, message: "This Test is already mapped to this Exam." });
        return;
    }

    // Create a new Test
    const newPracticeTest = await prisma.practiceTest.create({
        data: {
            title: test.title || "Test",
            examId,
            duration: 30,
            marks: test.testQuestions.length,
            language: test.language || "English",
            createdById: user.id,
            institutionId: user.institutionId,
            testId: testId
        }
    });

    res.status(200).json({
        success: true,
        message: "Test mapped successfully!",
        data: {
            ...newPracticeTest,
            createdAt: toIST(newPracticeTest.createdAt),
            updatedAt: toIST(newPracticeTest.updatedAt)
        }
    });
});