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

// Helper to delete file
const deleteFile = (filePath: string) => {
    if (!filePath) return;
    try {
        const fileName = path.basename(filePath);
        const absolutePath = path.join(process.cwd(), "uploads", fileName);
        if (fs.existsSync(absolutePath)) {
            fs.unlinkSync(absolutePath);
        }
    } catch (error) {
        console.error("Error deleting file:", error);
    }
};

const getStudentCurrentExamId = async (req: Request) => {
    const user = req.user as any;
    if (user?.role !== "STUDENT") return null;
    if (!user?.studentInstitutionId) return null;

    const studentInstitution = await prisma.studentInstitution.findFirst({
        where: { id: user.studentInstitutionId, deletedAt: null },
        select: { examsId: true },
    });

    return studentInstitution?.examsId ?? null;
};

// Create Notification
export const createNotification = asyncHandler(async (req: Request, res: Response) => {
    const user = req.user;
    const { exam, title, description, date } = req.body;
    const reqFiles = req.files as Express.Multer.File[];
    const files = reqFiles ? reqFiles.map(file => toPublicPath(file.path)) : [];

    if (!exam || !title || !description) {
        files.forEach(f => deleteFile(f));
        res.status(400).json({ message: "All fields are required" });
        return;
    }

    const newNotification = await prisma.notification.create({
        data: {
            institutionId: user.institutionId,
            examId: exam,
            title,
            description,
            date: date ? new Date(date) : new Date(),
            files,
        },
        include: { exam: { select: { id: true, examName: true } } }
    });

    // Notify students
    try {
        const students = await prisma.studentInstitution.findMany({
            where: { examsId: exam, institutionId: user.institutionId, deletedAt: null },
            select: {
                student: {
                    select: {
                        fcmToken: true
                    }
                }
            }
        })
        if (students.length > 0) {
            const tokens = students.map(s => s?.student?.fcmToken).filter((token): token is string => !!token);
            if (tokens.length > 0) {
                await sendNotification(
                    tokens,
                    newNotification.exam.examName,
                    description || "New Notification Added",
                    { type: "notification" }
                );
            }
        }
    } catch (notifyError) {
        console.error("Failed to send push notifications:", notifyError);
    }

    res.status(201).json({
        message: "Notification created successfully",
        data: {
            ...newNotification,
            date: toIST(newNotification.date),
            createdAt: toIST(newNotification.createdAt),
            updatedAt: toIST(newNotification.updatedAt),
            files: (newNotification.files as string[])?.map(f => `${getHost()}${f}`) || []
        },
    });
});

// Get All Notifications
export const getAllNotifications = asyncHandler(async (req: Request, res: Response) => {
    const user = req.user;
    const { search = "", page = "1", limit = "10", exam, type } = req.query;

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

    const where: any = {};
    if (search) {
        where.OR = [
            { title: { contains: search as string } },
            { description: { contains: search as string } },
            { exam: { examName: { contains: search as string } } },
        ];
    }

    const currentExamId = exam ? null : await getStudentCurrentExamId(req);
    if (exam) {
        where.examId = exam as string;
    } else if (currentExamId && user.role === "STUDENT") {
        where.examId = currentExamId;
    }

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

    if (type === 'link') {
        whereClause.notesLinkId = { not: null };
    } else {
        whereClause.notesLinkId = null;
    }

    const [total, notifications] = await Promise.all([
        prisma.notification.count({ where: whereClause }),
        prisma.notification.findMany({
            where: whereClause,
            include: { exam: { select: { id: true, examName: true } } },
            orderBy: { createdAt: 'desc' },
            skip,
            take: limitNum,
        }),
    ]);

    const processedData = notifications.map((item) => ({
        ...item,
        date: toIST(item.date),
        createdAt: toIST(item.createdAt),
        updatedAt: toIST(item.updatedAt),
        exam: item.examId,
        examName: item.exam?.examName,
        files: ((item.files as string[]) || []).map(f => `${getHost()}${f}`),
        redirectUrl: item.redirectUrl,
        isRead: item.isRead,
    }));

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

// Get Notification by ID
export const getNotificationById = asyncHandler(async (req: Request, res: Response) => {
    const user = req.user;
    const { id } = req.params;
    const currentExamId = await getStudentCurrentExamId(req);

    const notification = await prisma.notification.findUnique({
        where: {
            id,
            institutionId: user.institutionId,
            ...(user.role === "STUDENT" && currentExamId ? { examId: currentExamId } : {}),
        },
        include: { exam: { select: { examName: true } } }
    });

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

    res.status(200).json({
        data: {
            ...notification,
            date: toIST(notification.date),
            createdAt: toIST(notification.createdAt),
            updatedAt: toIST(notification.updatedAt),
            files: ((notification.files as string[]) || []).map(f => `${getHost()}${f}`)
        }
    });
});

// Update Notification
export const updateNotification = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;
    const { exam, title, description, date, existingFiles } = req.body;
    const user = req.user;

    const currentNotification = await prisma.notification.findUnique({ where: { id, institutionId: user.institutionId } });
    if (!currentNotification) {
        res.status(404).json({ message: "Notification not found" });
        return;
    }

    const updates: any = {};
    if (title) updates.title = title;
    if (description) updates.description = description;
    if (date) updates.date = new Date(date);
    if (exam) updates.examId = exam;

    let finalFiles: string[] = [];
    if (existingFiles) {
        const paths = Array.isArray(existingFiles) ? existingFiles : [existingFiles];
        finalFiles = paths.map(p => p.includes(getHost()) ? p.replace(getHost(), "") : p);
    }

    if (req.files && (req.files as Express.Multer.File[]).length > 0) {
        const newFiles = (req.files as Express.Multer.File[]).map(file => toPublicPath(file.path));
        finalFiles = [...finalFiles, ...newFiles];
    }

    // Delete removed files
    const filesToDelete = ((currentNotification.files as string[]) || []).filter(f => !finalFiles.includes(f));
    filesToDelete.forEach(f => deleteFile(f));

    updates.files = finalFiles;

    const updatedNotification = await prisma.notification.update({
        where: { id },
        data: updates,
        include: { exam: { select: { examName: true } } }
    });

    res.status(200).json({
        message: "Notification updated successfully",
        data: {
            ...updatedNotification,
            date: toIST(updatedNotification.date),
            createdAt: toIST(updatedNotification.createdAt),
            updatedAt: toIST(updatedNotification.updatedAt),
            files: (updatedNotification.files as string[])?.map(f => `${getHost()}${f}`) || []
        },
    });
});

// Delete Notification
export const deleteNotification = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;

    const notification = await prisma.notification.findUnique({
        where: { id },
    });

    if (!notification) {
        res.status(404);
        throw new Error("Notification not found");
    }

    if (notification.files) {
        (notification.files as string[]).forEach((file) => deleteFile(file));
    }

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

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

// Mark Notification as Read
export const markNotificationAsRead = asyncHandler(async (req: Request, res: Response) => {
    const { id } = req.params;
    const user = req.user;

    const notification = await prisma.notification.findUnique({
        where: { id },
    });

    if (!notification) {
        res.status(404);
        throw new Error("Notification not found");
    }

    if (notification.institutionId !== user.institutionId && user.role !== "ADMIN") {
        res.status(403);
        throw new Error("You do not have permission to mark this notification as read");
    }

    await prisma.notification.update({
        where: { id },
        data: { isRead: true },
    });

    res.status(200).json({ success: true, message: "Notification marked as read" });
});

// Mark All Notifications as Read
export const markAllNotificationsAsRead = asyncHandler(async (req: Request, res: Response) => {
    const user = req.user;

    await prisma.notification.updateMany({
        where: { 
            institutionId: user.institutionId,
            isRead: false
        },
        data: { isRead: true },
    });

    res.status(200).json({ success: true, message: "All notifications marked as read" });
});
