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

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

    if (!subjectName) {
        res.status(400).json({ message: "Subject name is required" });
        return;
    }

    const existing = await prisma.subject.findFirst({
        where: { institutionId: user.institutionId, subjectName: { equals: String(subjectName).trim() } }
    });

    if (existing) {
        res.status(400).json({ message: "Subject name already exists" });
        return;
    }
    const topicSet = {};
    if (Array.isArray(topics)) {
        topics.forEach((t: string) => {
            topicSet[t.trim().toLowerCase()] = true;
        });
        if (topics.length !== Number(Object.keys(topicSet).length)) {
            res.status(400).json({ message: "Duplicate topics found" });
            return;
        }
    }

    const subject = await prisma.subject.create({
        data: {
            subjectName,
            language: language || null,
            topics: Array.isArray(topics) ? topics.map((t: string) => t?.trim()) : [],
            institutionId: user.institutionId
        }
    });

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

export const getSubjects = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { search = "", page = "1", limit = "10", institutionId = "", includeShared = "false", testExamId, mapFilter } = req.query;

    const pageNum = Number(page);
    const limitNum = Number(limit);
    const skip = (pageNum - 1) * limitNum;

    const searchString = (search as string)?.trim() || "";
    const searchWords = searchString.split(/\s+/).filter(Boolean);

    const where: any = {};

    if (user.role === "ADMIN") {
        if (institutionId) {
            const instRec = await prisma.institution.findFirst({
                where: { userId: String(institutionId) },
                select: { id: true }
            });
            where.institutionId = instRec?.id ?? String(institutionId);
        }
    } else {
        if (includeShared === "true") {
            const baseInstCondition = user.role === "STAFF"
                ? { AND: [{ institutionId: user.institutionId }, { staffs: { some: { userId: user.id } } }] }
                : { institutionId: user.institutionId };

            where.OR = [
                baseInstCondition,
                {
                    tests: {
                        some: {
                            institutionId: user.institutionId,
                            referenceSourceId: { not: null }
                        }
                    }
                }
            ];
        } else {
            where.institutionId = user.institutionId;
            if (user.role === "STAFF") {
                where.staffs = { some: { userId: user.id } };
            }
        }
    }

    if (testExamId) {
        const mappedTests = await prisma.practiceTest.findMany({
            where: { examId: String(testExamId), institutionId: user.institutionId },
            select: { testId: true }
        });
        const mappedIds = mappedTests.map(m => m.testId).filter(Boolean) as string[];

        const testCondition: any = {
            institutionId: user.institutionId,
        };

        if (mapFilter === "used") {
            testCondition.id = mappedIds.length > 0 ? { in: mappedIds } : { in: ["none"] };
        } else if (mapFilter === "available") {
            if (mappedIds.length > 0) testCondition.id = { notIn: mappedIds };
        }

        where.tests = { some: testCondition };
    }

    if (searchWords.length > 0) {
        where.AND = searchWords.map(word => ({
            subjectName: { contains: word }
        }));
    }

    const [data, total] = await Promise.all([
        prisma.subject.findMany({
            where,
            orderBy: {
                subjectName: 'asc',
            },
            skip,
            take: limitNum,
            include: {
                notesLinks: {
                    select: {
                        type: true,
                        fileUrl: true,
                    },
                },
                subjectsNotesToExam: {
                    select: {
                        exam: {
                            select: { id: true, examName: true }
                        }
                    },
                },
                institution: {
                    select: {
                        user: {
                            select: { institutionName: true }
                        }
                    }
                }
            } as any,
        }),
        prisma.subject.count({ where }),
    ]);

    res.json({
        data: data.map((subject: any) => {
            const pdfCount = subject.notesLinks?.reduce((acc: number, item: any) => {
                if (item.type !== "PDF") return acc;
                let count = 0;
                if (Array.isArray(item.files) && item.files.length > 0) {
                    count = item.files.length;
                } else if (item.fileUrl) {
                    if (Array.isArray(item.fileUrl)) {
                        count = item.fileUrl.length;
                    } else {
                        count = 1;
                    }
                }
                return acc + count;
            }, 0) || 0;
            const linkCount = subject.notesLinks?.filter((item: any) => item.type === "LINK").length || 0;
            const exams = subject.subjectsNotesToExam?.map((se: any) => se.exam) || [];
            const isShared = user.role !== "ADMIN" && subject.institutionId !== user.institutionId;
            const sharedInstitutionName = isShared ? subject.institution?.user?.institutionName : null;

            return {
                ...subject,
                createdAt: toIST(subject.createdAt),
                updatedAt: toIST(subject.updatedAt),
                topics: subject.topics?.sort((a: string, b: string) =>
                    a.localeCompare(b, undefined, { sensitivity: "base" })
                ) || [],
                pdfCount,
                linkCount,
                exams,
                isShared,
                sharedInstitutionName
            };
        }),
        meta: {
            total,
            page: pageNum,
            limit: limitNum,
            totalPages: Math.ceil(total / limitNum),
        },
    });
});

export const getSubjectById = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const where: any = {
        id: req.params.id,
        institutionId: user.institutionId,
    }
    if (user.role === "STAFF") {
        where.staffs = { some: { userId: user.id } }
    }
    const subject = await prisma.subject.findFirst({
        where,
        include: {
            subjectsNotesToExam: {
                select: {
                    exam: {
                        select: { id: true, examName: true }
                    }
                },
            },
        } as any,
    });

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

    const exams = subject.subjectsNotesToExam?.map((se: any) => se.exam) || [];

    res.json({
        ...subject,
        exams,
        createdAt: toIST(subject.createdAt),
        updatedAt: toIST(subject.updatedAt),
    });
});

export const updateSubject = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    try {
        const existing = await prisma.subject.findFirst({
            where: { id: req.params.id, institutionId: user.institutionId },
            include: { 
                subjectsNotesToExam: true,
                notesLinks: { select: { type: true } }
            }
        });

        if (!existing) {
            res.status(404).json({ message: "Subject not found" });
            return;
        }
        const existingTopics = Array.isArray(existing.topics)
            ? (existing.topics as string[]).map(t => t.trim().toLowerCase())
            : [];
        const newTopics = Array.isArray(req.body.topics)
            ? (req.body.topics as string[]).map(t => String(t).trim().toLowerCase())
            : existingTopics;

        if (Array.isArray(req.body.topics)) {
            const topicSet = new Set<string>();
            req.body.topics.forEach((t: string) => {
                const normalized = String(t).trim().toLowerCase();
                if (topicSet.has(normalized)) {
                    res.status(400).json({ message: "Duplicate topics found" });
                    return;
                }
                topicSet.add(normalized);
            });

            if (topicSet.size !== newTopics.length) {
                res.status(400).json({ message: "Duplicate topics found" });
                return;
            }

            const deletedTopics = existingTopics.filter(topic => !newTopics.includes(topic));

            if (deletedTopics.length > 0) {
                const questionsExist = await prisma.questionBank.findFirst({
                    where: {
                        subjectId: req.params.id,
                        institutionId: user.institutionId,
                        topic: { in: deletedTopics }
                    }
                });

                if (questionsExist) {
                    res.status(400).json({
                        message: questionsExist.topic + "- Topic cannot be deleted as it has questions"
                    });
                    return;
                }
            }
        }

        const examIds = Array.isArray(req.body.examIds)
            ? Array.from(new Set(req.body.examIds.filter(Boolean))) as string[]
            : [];

        const subject = await prisma.subject.update({
            where: { id: req.params.id },
            data: {
                subjectName: req.body.subjectName ?? existing.subjectName,
                language: req.body.language !== undefined ? (req.body.language || null) : existing.language,
                topics: newTopics,
                subjectsNotesToExam: Array.isArray(req.body.examIds)
                    ? {
                        deleteMany: {},
                        create: examIds.map((id: string) => ({ examId: id })),
                    }
                    : undefined,
            } as any,
        });

        if (examIds.length > 0) {
            const existingExamIds = existing.subjectsNotesToExam?.map((se: any) => se.examId) || [];
            const newExamIds = examIds.filter(id => !existingExamIds.includes(id));

            if (newExamIds.length > 0) {
                try {
                    const students = await prisma.studentInstitution.findMany({
                        where: { examsId: { in: newExamIds }, institutionId: user.institutionId },
                        select: { student: { select: { fcmToken: true } } },
                    });

                    const tokens = students.map((s: any) => s.student?.fcmToken).filter((token): token is string => !!token);

                    if (tokens.length > 0) {
                        const visibleMaterials = existing.notesLinks?.filter((n: any) => n.verification !== "REJECTED") || [];
                        const pdfCount = visibleMaterials.filter((n: any) => n.type === "PDF").length;
                        const linkCount = visibleMaterials.filter((n: any) => n.type === "LINK").length;

                        let pushTitle = `Study Materials have been uploaded for "${subject.subjectName}"`;
                        if (pdfCount > 0 && linkCount > 0) {
                            pushTitle = `Notes & Links have been uploaded for "${subject.subjectName}"`;
                        } else if (pdfCount > 0) {
                            pushTitle = `Notes have been uploaded for "${subject.subjectName}"`;
                        } else if (linkCount > 0) {
                            pushTitle = `Links have been uploaded for "${subject.subjectName}"`;
                        }

                        await sendNotification(
                            tokens,
                            pushTitle,
                            "New materials have been published for your exam.",
                            { 
                                type: "subject_published", 
                                pdfCount: String(pdfCount), 
                                linkCount: String(linkCount) 
                            }
                        );
                    }
                } catch (error) {
                    console.error("Failed to send push notifications for mapped subject:", error);
                }
            }
        }

        res.json({
            message: "Subject updated successfully",
            data: {
                ...subject,
                createdAt: toIST(subject.createdAt),
                updatedAt: toIST(subject.updatedAt),
            },
        });
    } catch (error) {
        res.status(500).json({ message: "Error updating subject" });
    }
});

export const deleteSubject = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const where: any = {
        id: req.params.id,
        institutionId: user.institutionId,
    }
    if (user.role === "STAFF") {
        where.staffs = { some: { userId: user.id } }
    }
    const subject = await prisma.subject.findFirst({
        where
    });
    if (!subject) {
        res.status(404).json({ message: "Subject not found" });
        return;
    }

    const questions = await prisma.questionBank.findMany({
        where: {
            subjectId: req.params.id,
            institutionId: user.institutionId
        }
    });

    if (questions.length > 0) {
        res.status(400).json({
            message: "Subject cannot be deleted as it has questions"
        });
        return;
    }

    await prisma.subject.delete({
        where: { id: req.params.id }
    });

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

export const getSubjectsQuestions = asyncHandler(async (req: Request, res: Response) => {
    const user = (req as any).user;
    const { search = "", page = "1", limit = "10" } = req.query;

    const pageNum = Number(page);
    const limitNum = Number(limit);
    const skip = (pageNum - 1) * limitNum;

    const where: any = {
        institutionId: user.institutionId,
        subjectName: { contains: search as string }
    };

    if (user.role === "STAFF") {
        where.staffs = { some: { userId: user.id } }
    }

    const [subjects, totalSubjects, totalQuestions, totalTests] = await Promise.all([
        prisma.subject.findMany({
            where,
            include: {
                _count: {
                    select: {
                        questions: {
                            where: { institutionId: user.institutionId }
                        },
                        tests: {
                            where: { institutionId: user.institutionId }
                        }
                    },
                },
            },
            orderBy: { subjectName: "asc" },
            skip,
            take: limitNum,
        }),
        prisma.subject.count({ where }),
        prisma.questionBank.count({
            where: {
                institutionId: user.institutionId,
                subject: {
                    subjectName: { contains: search as string },
                },
            },
        }),
        prisma.test.count({
            where: {
                institutionId: user.institutionId,
                subject: {
                    subjectName: { contains: search as string },
                },
            },
        }),
    ]);

    const data = subjects.map((s) => ({
        id: s.id,
        subjectName: s.subjectName,
        language: s.language,
        topicsCount: Array.isArray(s.topics) ? (s.topics as string[]).length : 0,
        questionsCount: s._count.questions,
        testsCount: s._count.tests,
    }));

    res.status(200).json({
        data,
        totalQuestions,
        totalTests,
        meta: {
            total: totalSubjects,
            page: pageNum,
            limit: limitNum,
            totalPages: Math.ceil(totalSubjects / limitNum),
        },
    });
});

export const getSubjectTopics = asyncHandler(async (req: Request, res: Response) => {
    const { subjectId } = req.params;
    const { search = "", page = "1", limit = "5" } = req.query;

    const pageNum = Number(page);
    const limitNum = Number(limit);
    const skip = (pageNum - 1) * limitNum;

    const subject = await prisma.subject.findUnique({
        where: { id: subjectId }
    });

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

    const topics: string[] = Array.isArray(subject.topics)
        ? (subject.topics as string[])
        : [];

    let filteredTopics = topics.sort((a, b) =>
        a.localeCompare(b, undefined, { sensitivity: "base" })
    );

    if (search) {
        const lowerSearch = (search as string).toLowerCase();
        filteredTopics = filteredTopics.filter((topic: string) =>
            topic.toLowerCase().includes(lowerSearch)
        );
    }

    const questionCounts = await prisma.questionBank.groupBy({
        by: ['topic'],
        where: { subjectId: subjectId },
        _count: {
            id: true
        }
    });
    const countMap: Record<string, number> = {};
    questionCounts.forEach((q) => {
        if (q.topic) {
            countMap[String(q.topic).trim().toLowerCase()] = q._count.id;
        }
    });

    const topicsWithCount = filteredTopics.map((topic: string) => ({
        topic,
        questionsCount: countMap[String(topic).trim().toLowerCase()] || 0,
    }));
    const paginatedTopics = topicsWithCount.slice(skip, skip + limitNum);
    res.status(200).json({
        subjectId,
        topics: paginatedTopics,
        meta: {
            total: topicsWithCount.length,
            page: pageNum,
            limit: limitNum,
            totalPages: Math.ceil(topicsWithCount.length / limitNum),
        },
    });
});
