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

// Create a test for mapping
export const createTest = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { title, subjectId, topic, questionIds, language, duration, marks } = req.body;

    if (!questionIds || !Array.isArray(questionIds) || questionIds.length === 0) {
        res.status(400).json({ message: "At least one Question ID is required" });
        return;
    }

    const testTitle = title || "New Test";
    const existingTest = await prisma.test.findFirst({
        where: {
            title: testTitle,
            institutionId: user.institutionId
        }
    });

    if (existingTest) {
        res.status(400).json({ message: "Test with this title already exists in the institution" });
        return;
    }

    const test = await prisma.test.create({
        data: {
            title: title || "New Test",
            subjectId,
            topic,
            language,
            duration: duration ? Number(duration) : undefined,
            marks: marks ? Number(marks) : undefined,
            institutionId: user.institutionId,
            createdById: user.id,
            testQuestions: {
                create: questionIds.map((qId: string, index: number) => ({
                    questionId: qId,
                }))
            }
        },
        include: {
            testQuestions: true
        }
    });

    res.status(201).json({
        message: "Test created successfully",
        data: {
            ...test,
            createdAt: toIST(test.createdAt),
            updatedAt: toIST(test.updatedAt),
        },
    });
});

// Get test by subject
export const getTestsBySubject = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { subjectId } = req.params;

    const tests = await prisma.test.findMany({
        where: {
            subjectId,
            institutionId: user.institutionId
        },
        include: {
            _count: {
                select: { testQuestions: true, practiceTests: true }
            },
            practiceTests: {
                include: {
                    _count: { select: { examResults: true } }
                }
            }
        },
        orderBy: {
            createdAt: "desc"
        }
    });

    res.status(200).json({
        data: tests.map(test => {
            const hasResults = test.practiceTests.some(pt => pt._count.examResults > 0);
            const { practiceTests, ...restTest } = test;

            return {
                ...restTest,
                hasResults,
                createdAt: toIST(test.createdAt),
                updatedAt: toIST(test.updatedAt),
            };
        })
    });
});

// Get test by id
export const getTestById = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { id } = req.params;

    let test = await prisma.test.findFirst({
        where: { id, institutionId: user.institutionId },
        include: {
            subject: { select: { id: true, subjectName: true, language: true } },
            testQuestions: {
                include: { question: true }
            }
        }
    });

    if (test && test.referenceSourceId) {
        const sourceTest = await prisma.test.findUnique({
            where: { id: test.referenceSourceId },
            include: { testQuestions: { include: { question: true } } }
        });
        if (sourceTest) {
            test.testQuestions = sourceTest.testQuestions;
        }
    }

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

    res.status(200).json({
        data: {
            ...test,
            createdAt: toIST(test.createdAt),
            updatedAt: toIST(test.updatedAt),
        }
    });
});

// Get test questions
export const getTestQuestions = asyncHandler(async (req: Request, res: Response) => {
    const { testId } = req.params;
    const page = parseInt(req.query.page as string) || 1;
    const limit = parseInt(req.query.limit as string) || 10;
    const search = req.query.search as string || "";

    const skip = (page - 1) * limit;

    let targetTestId = testId;
    const test = await prisma.test.findUnique({
        where: { id: testId },
        select: { referenceSourceId: true }
    });
    
    if (test?.referenceSourceId) {
        targetTestId = test.referenceSourceId;
    }

    const testQuestions = await prisma.testQuestion.findMany({
        where: {
            testId: targetTestId,
            question: {
                questionText: { contains: search }
            }
        },
        include: {
            question: true
        },
        skip,
        take: limit
    });

    const total = await prisma.testQuestion.count({
        where: {
            testId: targetTestId,
            question: {
                questionText: { contains: search }
            }
        }
    });

    res.status(200).json({
        questions: testQuestions.map(tq => tq.question),
        meta: {
            page,
            limit,
            total,
            totalPages: Math.ceil(total / limit)
        }
    });
});

// Get all test
export const getAllTests = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;

    const { search, subjectId, excludeExamId, page = "1", limit = "10", examId, mapped, language } = req.query;

    const pageNum = parseInt(page as string) || 1;
    const limitNum = parseInt(limit as string) || 10;
    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 (subjectId) {
        if (Array.isArray(subjectId)) {
            where.subjectId = { in: subjectId };
        } else {
            where.subjectId = subjectId as string;
        }
    }

    if (excludeExamId) {
        where.practiceTests = {
            none: {
                examId: excludeExamId as string
            }
        };
    }

    let mappedTestIds: string[] = [];
    if (examId) {
        const existingMappedTests = await prisma.practiceTest.findMany({
            where: {
                examId: examId as string,
                institutionId: user.institutionId,
            },
            select: { testId: true },
        });

        mappedTestIds = existingMappedTests
            .map((item: any) => item.testId)
            .filter(Boolean) as string[];

        if (mapped === "used") {
            if (mappedTestIds.length > 0) {
                where.id = { in: mappedTestIds };
            } else {
                where.id = { in: [] };
            }
        } else if (mapped === "available") {
            if (mappedTestIds.length > 0) {
                where.id = { notIn: mappedTestIds };
            }
        }
    }

    const [tests, totalCount] = await prisma.$transaction([
        prisma.test.findMany({
            where,
            include: {
                subject: { select: { subjectName: true, language: true } },
                _count: { select: { testQuestions: true, practiceTests: true } },
                practiceTests: {
                    include: {
                        _count: { select: { examResults: true } }
                    }
                },
                referenceInstitution: {
                    select: { user: { select: { institutionName: true, firstName: true, lastName: true } } }
                }
            },
            orderBy: { createdAt: "desc" },
            skip: skip,
            take: limitNum,
        }),
        prisma.test.count({ where })
    ]);

    const mappedIdSet = new Set(mappedTestIds);

    let stats: { totalCount: number; mappedCount: number; availableCount: number } | undefined = undefined;

    if (examId) {
        const subIdStr = typeof subjectId === "string" ? subjectId : undefined;

        const mappedCount = await prisma.practiceTest.count({
            where: {
                examId: examId as string,
                institutionId: user.institutionId,
                test: subIdStr ? { subjectId: subIdStr } : undefined
            }
        });

        const totalTestsCount = await prisma.test.count({
            where: {
                institutionId: user.institutionId,
                subjectId: subIdStr ? subIdStr : undefined
            }
        });

        stats = {
            totalCount: totalTestsCount,
            mappedCount,
            availableCount: Math.max(0, totalTestsCount - mappedCount)
        };
    }

    const resolvedTests = await Promise.all(tests.map(async (test) => {
        const hasResults = test.practiceTests?.some(pt => pt._count?.examResults > 0) ?? false;
        
        let questionCount = test._count.testQuestions;
        if (test.referenceSourceId) {
            questionCount = await prisma.testQuestion.count({
                where: { testId: test.referenceSourceId }
            });
        }

        const { practiceTests, ...restTest } = test;

        return {
            ...restTest,
            _count: {
                ...test._count,
                testQuestions: questionCount
            },
            isAlreadyMapped: examId ? mappedIdSet.has(test.id) : false,
            hasResults,
            createdAt: toIST(test.createdAt),
            updatedAt: toIST(test.updatedAt),
        };
    }));

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

// Update test
export const updateTest = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { id } = req.params;
    const { title, topic, questionIds, language, duration, marks } = req.body;

    const existing = await prisma.test.findFirst({
        where: { id, institutionId: user.institutionId },
        include: { sharedCopies: { select: { id: true } } }
    });

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

    if (existing.referenceSourceId) {
        res.status(403).json({ message: "Cannot edit a shared test" });
        return;
    }

    if (title && title !== existing.title) {
        const titleExists = await prisma.test.findFirst({
            where: {
                title,
                institutionId: user.institutionId,
                id: { not: id }
            }
        });

        if (titleExists) {
            res.status(400).json({ message: "Test with this title already exists in the institution" });
            return;
        }
    }

    const testIdsToUpdate = [id, ...(existing.sharedCopies?.map((c) => c.id) || [])];

    await prisma.testQuestion.deleteMany({ where: { testId: { in: testIdsToUpdate } } });

    const actualMarks = questionIds.length;
    const testTitle = title || existing.title;
    const testTopic = topic !== undefined ? topic : existing.topic;
    const testLanguage = language ?? existing.language;
    const testDuration = duration ? Number(duration) : existing.duration;

    await prisma.test.updateMany({
        where: { id: { in: testIdsToUpdate } },
        data: {
            title: testTitle,
            topic: testTopic,
            language: testLanguage,
            duration: testDuration,
            marks: actualMarks,
        }
    });

    const testQuestionData = testIdsToUpdate.flatMap(tId =>
        questionIds.map((qId: string) => ({
            testId: tId,
            questionId: qId
        }))
    );
    await prisma.testQuestion.createMany({ data: testQuestionData });

    const test = await prisma.test.findFirst({
        where: { id },
        include: { testQuestions: true }
    });

    await prisma.practiceTest.updateMany({
        where: { testId: { in: testIdsToUpdate } },
        data: {
            title: testTitle,
            language: testLanguage,
            duration: testDuration,
            marks: actualMarks,
        }
    });

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

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

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

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

    if (test.referenceSourceId) {
        res.status(403).json({ message: "Cannot delete a shared test" });
        return;
    }

    const mappedPracticeTests = await prisma.practiceTest.findMany({ where: { testId: id } });
    const ptIds = mappedPracticeTests.map(pt => pt.id);

    const sharedCopies = await prisma.test.findMany({ where: { referenceSourceId: id }, select: { id: true } });
    const sharedTestIds = sharedCopies.map(s => s.id);

    let allPtIds = [...ptIds];
    if (sharedTestIds.length > 0) {
        const sharedPracticeTests = await prisma.practiceTest.findMany({ where: { testId: { in: sharedTestIds } } });
        allPtIds = [...allPtIds, ...sharedPracticeTests.map(pt => pt.id)];
    }

    if (allPtIds.length > 0) {
        const hasResults = await prisma.examResult.findFirst({
            where: { practiceTestId: { in: allPtIds } }
        });

        if (hasResults) {
            res.status(400).json({ message: "Cannot delete this test because it (or its shared copies) is already in use by students." });
            return;
        }
        await prisma.practiceTest.deleteMany({ where: { testId: { in: [id, ...sharedTestIds] } } });
    }

    if (sharedTestIds.length > 0) {
        await prisma.testQuestion.deleteMany({ where: { testId: { in: sharedTestIds } } });
        await prisma.test.deleteMany({ where: { id: { in: sharedTestIds } } });
    }

    await prisma.testQuestion.deleteMany({
        where: { testId: id }
    });

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

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

export const getAvailableTestQuestionsCount = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { subjectIds, topics, difficulty, language, currentTestId } = req.body;

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

    if (subjectIds && Array.isArray(subjectIds) && subjectIds.length > 0) {
        whereBase.subjectId = { in: subjectIds };
    } else if (req.body.subjectId) {
        whereBase.subjectId = req.body.subjectId;
    }
    
    if (topics && Array.isArray(topics) && topics.length > 0) {
        whereBase.topic = { in: topics };
    } else if (req.body.topic) {
        whereBase.topic = { in: Array.isArray(req.body.topic) ? req.body.topic : [req.body.topic] };
    }
    if (difficulty) whereBase.difficulty = difficulty;
    if (language) whereBase.language = language;

    const whereAvailable: any = { ...whereBase };
    if (currentTestId) {
        whereAvailable.testQuestions = { none: { testId: { not: currentTestId } } };
    } else {
        whereAvailable.testQuestions = { none: {} };
    }

    const whereUsed: any = { ...whereBase, testQuestions: { some: {} } };

    const availableCount = await prisma.questionBank.count({ where: whereAvailable });
    const usedCount = await prisma.questionBank.count({ where: whereUsed });
    const totalCount = availableCount + usedCount;

    res.status(200).json({
        message: "Count fetched successfully",
        available: availableCount,
        used: usedCount,
        total: totalCount,
        count: availableCount
    });
});

export const generateRandomTestQuestions = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { subjectIds, topics, difficulty, language, count, isAlreadyUsed, currentTestId } = req.body;

    const countNum = Number(count);
    if (!countNum || countNum <= 0 || countNum > 200) {
        res.status(400).json({ message: "Count must be a number between 1 and 200" });
        return;
    }

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

    if (subjectIds && Array.isArray(subjectIds) && subjectIds.length > 0) {
        where.subjectId = { in: subjectIds };
    } else if (req.body.subjectId) {
        where.subjectId = req.body.subjectId;
    }

    if (topics && Array.isArray(topics) && topics.length > 0) {
        where.topic = { in: topics };
    } else if (req.body.topic) {
        where.topic = { in: Array.isArray(req.body.topic) ? req.body.topic : [req.body.topic] };
    }
    
    if (difficulty) where.difficulty = difficulty;
    if (language) where.language = language;

    if (isAlreadyUsed === "0") {
        if (currentTestId) {
            where.testQuestions = { none: { testId: { not: currentTestId } } };
        } else {
            where.testQuestions = { none: {} };
        }
    } else if (isAlreadyUsed === "1") {
        where.testQuestions = { some: {} };
    }

    const totalMatching = await prisma.questionBank.count({ where });
    if (totalMatching === 0) {
        res.status(404).json({ message: "No questions found matching the criteria" });
        return;
    }

    const allIds = await prisma.questionBank.findMany({
        where,
        select: { id: true, topic: true }
    });

    const groupedByTopic: Record<string, { id: string }[]> = {};
    for (const q of allIds) {
        const key = q.topic || 'unknown';
        if (!groupedByTopic[key]) {
            groupedByTopic[key] = [];
        }
        groupedByTopic[key].push(q);
    }

    for (const key in groupedByTopic) {
        groupedByTopic[key].sort(() => 0.5 - Math.random());
    }

    const selectedIds: string[] = [];
    const availableTopicKeys = Object.keys(groupedByTopic);

    let i = 0;
    while (selectedIds.length < countNum && availableTopicKeys.length > 0) {
        const currentIndex = i % availableTopicKeys.length;
        const topicKey = availableTopicKeys[currentIndex];
        const group = groupedByTopic[topicKey];

        if (group.length > 0) {
            selectedIds.push(group.pop()!.id);
            i++;
        } else {
            availableTopicKeys.splice(currentIndex, 1);
        }
    }

    const questions = await prisma.questionBank.findMany({
        where: { id: { in: selectedIds } },
        include: {
            subject: { select: { subjectName: true } }
        }
    });

    res.status(200).json({
        message: "Questions generated successfully",
        totalAvailable: totalMatching,
        data: questions.map(q => ({
            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}` : "",
            })),
            marks: q.marks,
            difficulty: q.difficulty,
            subject: q.subject?.subjectName || "",
            subjectId: q.subjectId,
            topic: q.topic || "",
            language: q.language,
            correctAnswer: q.correctAnswer,
            explanation: q.explanation,
            explanationImage: q.explanationImage ? `${getHost()}${q.explanationImage}` : "",
            sourceType: "auto"
        }))
    });
});

export const replaceQuestion = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { subjectIds, topics, difficulty, language, currentTestId, existingQuestionIds = [], isAlreadyUsed } = req.body;

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

    if (subjectIds && Array.isArray(subjectIds) && subjectIds.length > 0) {
        where.subjectId = { in: subjectIds };
    } else if (req.body.subjectId) {
        where.subjectId = req.body.subjectId;
    }
    
    if (topics && Array.isArray(topics) && topics.length > 0) {
        where.topic = { in: topics };
    } else if (req.body.topic) {
        where.topic = { in: Array.isArray(req.body.topic) ? req.body.topic : [req.body.topic] };
    }
    if (difficulty) where.difficulty = difficulty;
    if (language) where.language = language;

    const excludeIds = Array.isArray(existingQuestionIds) ? existingQuestionIds : [];
    where.id = { notIn: excludeIds };

    if (isAlreadyUsed === "0") {
        if (currentTestId) {
            where.testQuestions = { none: { testId: { not: currentTestId } } };
        } else {
            where.testQuestions = { none: {} };
        }
    } else if (isAlreadyUsed === "1") {
        where.testQuestions = { some: {} };
    }

    const alternativeIds = await prisma.questionBank.findMany({
        where,
        select: { id: true }
    });

    if (alternativeIds.length === 0) {
        res.status(404).json({ message: "No alternative questions found matching your criteria" });
        return;
    }

    const randomPick = alternativeIds[Math.floor(Math.random() * alternativeIds.length)];

    const question = await prisma.questionBank.findUnique({
        where: { id: randomPick.id },
        include: {
            subject: { select: { subjectName: true } }
        }
    });

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

    res.status(200).json({
        message: "Question replaced successfully",
        data: {
            id: question.id,
            questionText: question.questionText,
            questionImage: question.questionImage ? `${getHost()}${question.questionImage}` : "",
            options: (question.options as any || []).map((opt: any) => ({
                option: String(opt?.option) ?? "",
                optionImage: opt.optionImage ? `${getHost()}${opt.optionImage}` : "",
            })),
            marks: question.marks,
            difficulty: question.difficulty,
            subject: question.subject?.subjectName || "",
            subjectId: question.subjectId,
            topic: question.topic || "",
            language: question.language,
            correctAnswer: question.correctAnswer,
            explanation: question.explanation,
            explanationImage: question.explanationImage ? `${getHost()}${question.explanationImage}` : "",
        }
    });
});