import type { Request, Response } from "express";
import { getHost, toPublicPath } from "../utils/utils.ts";
import { PrismaClient, NotesType, NotesLinksStatus } from "@prisma/client";
import { sendNotification } from "../config/firebase.ts";

const prisma = new PrismaClient();

const formatFileUrl = (filePath: string | null): string | null => {
  if (!filePath) return null;
  if (filePath.startsWith("http")) return filePath;
  const publicPath = toPublicPath(filePath);
  return publicPath ? `${getHost()}${publicPath}` : null;
};

// Helper to notify students based on subject mapping to exams
const notifyStudentsAboutNotes = async (institutionId: string, subjectId: string, subjectName: string, title: string, itemType: "PDF" | "LINK", count: number = 1) => {
  try {
    const mappedExams = await prisma.subjectsNotesToExam.findMany({
      where: { subjectId },
      select: { examId: true },
    });
    const examIds = mappedExams.map((m) => m.examId);

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

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

      if (tokens.length > 0) {
        const pushTitle = itemType === "PDF" 
            ? `Notes have been uploaded for "${subjectName}"` 
            : `Links have been uploaded for "${subjectName}"`;

        const payload = {
          title: pushTitle,
          body: count > 1
            ? `New ${itemType === "PDF" ? "PDF Materials" : "Links"} have been published.`
            : `New ${itemType === "PDF" ? "PDF Material" : "Link"} '${title}' has been published.`,
          data: { 
            type: itemType === "PDF" ? "notes" : "links", 
            pdfCount: itemType === "PDF" ? String(count) : "0",
            linkCount: itemType === "LINK" ? String(count) : "0"
          }
        };
        const fcmResponse = await sendNotification(
          tokens,
          payload.title,
          payload.body,
          payload.data
        );
        return { ...fcmResponse, sentPayload: payload };
      }
    }
    return { successCount: 0, failureCount: 0, invalidTokens: [], message: "No students to notify" };
  } catch (error) {
    console.error("Failed to send push notifications for notes:", error);
    return { successCount: 0, failureCount: 0, invalidTokens: [], error: String(error) };
  }
};

// Create notes and links
  export const createNotesLink = async (req: Request, res: Response) => {
  try {
    let { title, type, linkUrl, links, subjectId, institutionId, topics } = req.body;
    const userId = req.user?.id;

    if (!institutionId && req.user?.institutionId) {
      institutionId = req.user.institutionId;
    }

    if (!type || !subjectId || !institutionId) {
      return res.status(400).json({
        success: false,
        message: "Type, subjectId, and institutionId are required.",
      });
    }

    if (!userId) {
      return res.status(401).json({ success: false, message: "Unauthorized." });
    }

    let parsedTopics = null;
    if (topics) {
      try {
        parsedTopics = typeof topics === "string" ? JSON.parse(topics) : topics;
      } catch (e) {
        parsedTopics = topics;
      }
    }

    if (type === NotesType.LINK) {
      if (!links && !linkUrl) {
        return res.status(400).json({ success: false, message: "Link URL is required." });
      }

      const linksToCreate = links || [{ title, linkUrl }];

      await prisma.notesLinks.createMany({
        data: linksToCreate.map((l: any) => ({
          title: l.title,
          linkUrl: l.linkUrl,
          type: NotesType.LINK,
          topics: parsedTopics,
          subjectId,
          institutionId,
          createdById: userId,
          verification: NotesLinksStatus.PENDING,
        })),
      });

      const subject = await prisma.subject.findUnique({ where: { id: subjectId } });
      if (subject) {
        await notifyStudentsAboutNotes(
          institutionId,
          subjectId,
          subject.subjectName,
          linksToCreate[0].title,
          "LINK",
          linksToCreate.length
        );
      }

      return res.status(201).json({
        success: true,
        message: "Link(s) submitted successfully and pending admin approval.",
      });
    }

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

    let fileUrl: any = null;
    if (req.files && Array.isArray(req.files) && req.files.length > 0) {
      const filesArray = req.files.map((file: any) => ({
        name: file.originalname || file.filename,
        url: toPublicPath(file.path) || undefined,
      }));
      fileUrl = filesArray;
    } else if (!req.body.fileUrl) {
      return res.status(400).json({
        success: false,
        message: "PDF file is required.",
      });
    } else {
      fileUrl = req.body.fileUrl;
    }

    const notesLink = await prisma.notesLinks.create({
      data: {
        title,
        type: NotesType.PDF,
        fileUrl,
        topics: parsedTopics,
        subjectId,
        institutionId,
        createdById: userId,
        verification: NotesLinksStatus.VERIFIED,
      },
      include: { subject: { select: { subjectName: true } } },
    });

    await notifyStudentsAboutNotes(
      institutionId,
      subjectId,
      notesLink.subject.subjectName,
      title,
      "PDF"
    );

    const parsedFiles = (Array.isArray(notesLink.fileUrl) ? notesLink.fileUrl : []) as any[];
    const formattedFiles = parsedFiles.map((f: any) => ({ ...f, url: formatFileUrl(f.url) }));

    return res.status(201).json({
      success: true,
      message: "Notes uploaded successfully and is now available to students.",
      data: {
        ...notesLink,
        fileUrl: formattedFiles.length > 0 ? formattedFiles[0].url : null,
        files: formattedFiles,
      },
    });
  } catch (error: any) {
    console.error("Error creating notes/link:", error);
    return res.status(500).json({
      success: false,
      message: "Internal server error.",
      error: error.message,
    });
  }
};

// Get all notes & links
export const getAllNotesLinks = async (req: Request, res: Response) => {
  try {
    const user = req.user;
    if (!user) {
      return res.status(401).json({ success: false, message: "Unauthorized." });
    }

    const {
      subjectId,
      examId,
      institutionId,
      type,
      verification,
      search,
      title,
      page = 1,
      limit = 10,
    } = req.query;

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

    const whereCondition: any = {};

    if (user.role === "STUDENT") {
      whereCondition.verification = { not: NotesLinksStatus.REJECTED };

      const studentInstitutionId = user.studentInstitutionId;
      if (studentInstitutionId) {
        const studentInstitution = await prisma.studentInstitution.findUnique({
          where: { id: studentInstitutionId },
          select: { examsId: true, institutionId: true },
        });

        if (studentInstitution?.examsId) {
          const studentExamId = studentInstitution.examsId;
          whereCondition.institutionId = studentInstitution.institutionId;
          whereCondition.OR = [
            { subject: { subjectsNotesToExam: { some: { examId: studentExamId } } } },
          ];
        } else {
          whereCondition.institutionId = studentInstitution?.institutionId || "NONE";
        }
      }
    } else if (
      user.role === "INSTITUTION" ||
      user.role === "STAFF"
    ) {
      whereCondition.institutionId = user.institutionId;

      if (
        verification &&
        Object.values(NotesLinksStatus).includes(
          verification as NotesLinksStatus
        )
      ) {
        whereCondition.verification =
          verification as NotesLinksStatus;
      }
    } else if (user.role === "ADMIN") {
      if (institutionId)
        whereCondition.institutionId = String(institutionId);

      if (
        verification &&
        Object.values(NotesLinksStatus).includes(
          verification as NotesLinksStatus
        )
      ) {
        whereCondition.verification =
          verification as NotesLinksStatus;
      }

      whereCondition.subject = {
        subjectsNotesToExam: { some: {} },
      };
    }

    if (examId) {
      whereCondition.OR = [
        { subject: { subjectsNotesToExam: { some: { examId: String(examId) } } } }
      ];
    }

    if (subjectId) {
      whereCondition.subjectId = String(subjectId).trim();
    }

    if (
      type &&
      Object.values(NotesType).includes(type as NotesType)
    ) {
      whereCondition.type = type as NotesType;
    }

    const searchQuery = (title || search) as string;
    if (searchQuery) {
      whereCondition.title = {
        contains: String(searchQuery).trim()
      };
    }

    const [total, notesLinks] = await Promise.all([
      prisma.notesLinks.count({ where: whereCondition }),
      prisma.notesLinks.findMany({
        where: whereCondition,
        skip,
        take: limitNum,
        orderBy: { createdAt: "desc" },
        include: {
          subject: { select: { id: true, subjectName: true } },
          institution: { include: { user: { select: { institutionName: true } } } },
          _count: { select: { reports: true } },
        },
      }),
    ]);

    const formattedNotesLinks = notesLinks.map((note) => {
      const parsedFiles = (Array.isArray(note.fileUrl) ? note.fileUrl : []) as any[];
      const formattedFiles = parsedFiles.map((f: any) => ({ ...f, url: formatFileUrl(f.url) }));
        
      return {
        id: note.id,
        title: note.title,
        type: note.type,
        files: formattedFiles,
        linkUrl: note.linkUrl,
        topics: note.topics,
        verification: note.verification,
        rejectReason: note.rejectReason,
        subjectId: note.subjectId,
        subject: note.subject,
        institutionId: note.institutionId,
        institutionName: note.institution?.user?.institutionName,
        createdAt: note.createdAt,
        updatedAt: note.updatedAt,
        _count: note._count,
      };
    });

    return res.status(200).json({
      success: true,
      data: formattedNotesLinks,
      pagination: {
        total,
        page: pageNum,
        limit: limitNum,
        totalPages: Math.ceil(total / limitNum),
      },
    });
  } catch (error: any) {
    console.error("Error fetching notes/links:", error);
    return res.status(500).json({
      success: false,
      message: "Internal server error.",
      error: error.message,
    });
  }
};

// Get single notes & links by id
export const getNotesLinkById = async (req: Request, res: Response) => {
  try {
    const { id } = req.params;

    const notesLink = await prisma.notesLinks.findUnique({
      where: { id },
      include: {
        subject: { select: { id: true, subjectName: true } },
        reports: {
          include: {
            student: {
              select: {
                id: true,
                userId: true,
                user: {
                  select: {
                    id: true,
                    firstName: true,
                    lastName: true,
                    email: true,
                  },
                },
              },
            },
          },
        },
      },
    });

    if (!notesLink) {
      return res.status(404).json({ success: false, message: "Notes/Link not found." });
    }

    if (req.user?.role === "STUDENT" && notesLink.verification === NotesLinksStatus.REJECTED) {
      return res.status(403).json({ success: false, message: "Access denied. This note/link has been rejected." });
    }

    const parsedFiles = (Array.isArray(notesLink.fileUrl) ? notesLink.fileUrl : []) as any[];
    const formattedFiles = parsedFiles.map((f: any) => ({ ...f, url: formatFileUrl(f.url) }));

    const formattedNote = {
      id: notesLink.id,
      title: notesLink.title,
      type: notesLink.type,
      fileUrl: formattedFiles.length > 0 ? formattedFiles[0].url : null,
      files: formattedFiles,
      linkUrl: notesLink.linkUrl,
      topics: notesLink.topics,
      verification: notesLink.verification,
      rejectReason: notesLink.rejectReason,
      subjectId: notesLink.subjectId,
      subject: notesLink.subject,
      institutionId: notesLink.institutionId,
      createdAt: notesLink.createdAt,
      updatedAt: notesLink.updatedAt,
      reports: notesLink.reports,
    };

    return res.status(200).json({ success: true, data: formattedNote });
  } catch (error: any) {
    console.error("Error fetching notes/link detail:", error);
    return res.status(500).json({
      success: false,
      message: "Internal server error.",
      error: error.message,
    });
  }
};

// Update verification status (verify / reject) for links
export const updateVerificationStatus = async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const { verification, rejectReason } = req.body;

    if (!verification || !Object.values(NotesLinksStatus).includes(verification)) {
      return res.status(400).json({
        success: false,
        message: "Invalid verification status. Must be VERIFIED or REJECTED.",
      });
    }

    if (verification === NotesLinksStatus.REJECTED && !rejectReason) {
      return res.status(400).json({
        success: false,
        message: "Reason is required when rejecting a note/link.",
      });
    }

    const existingNote = await prisma.notesLinks.findUnique({ where: { id } });
    if (!existingNote) {
      return res.status(404).json({ success: false, message: "Notes/Link not found." });
    }


    if (existingNote.type === NotesType.PDF) {
      return res.status(400).json({
        success: false,
        message: "PDF notes are automatically verified and cannot have their status changed.",
      });
    }

    const updatedNotesLink = await prisma.notesLinks.update({
      where: { id },
      data: {
        verification,
        rejectReason: verification === NotesLinksStatus.REJECTED ? rejectReason : null,
      },
    });

    const parsedFiles = (Array.isArray(updatedNotesLink.fileUrl) ? updatedNotesLink.fileUrl : []) as any[];
    const formattedFiles = parsedFiles.map((f: any) => ({ ...f, url: formatFileUrl(f.url) }));
    const isApproved = verification === NotesLinksStatus.VERIFIED;
    const notificationTitle = isApproved ? "Link Approved" : "Link Rejected";
    const notificationDesc = isApproved 
      ? `Your link "${updatedNotesLink.title}" has approved by Admin.`
      : `Your link "${updatedNotesLink.title}" has rejected by Admin. Reason: ${rejectReason}`;

    await prisma.notification.create({
      data: {
        title: notificationTitle,
        description: notificationDesc,
        institutionId: updatedNotesLink.institutionId,
        redirectUrl: `/notes-and-links/${updatedNotesLink.subjectId}?tab=resource-links`, 
        isRead: false,
        notesLinkId: updatedNotesLink.id
      }
    });

    return res.status(200).json({
      success: true,
      message: `Notes/Link status updated to ${verification}.`,
      data: {
        ...updatedNotesLink,
        fileUrl: formattedFiles.length > 0 ? formattedFiles[0].url : null,
        files: formattedFiles,
      },
    });
  } catch (error: any) {
    console.error("Error updating verification status:", error);
    return res.status(500).json({
      success: false,
      message: "Internal server error.",
      error: error.message,
    });
  }
};

// Update notes and links
  export const updateNotesLink = async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const user = req.user;
    const { title, type, linkUrl, subjectId, topics } = req.body;

    const existingNote = await prisma.notesLinks.findUnique({ where: { id } });
    if (!existingNote) {
      return res.status(404).json({ success: false, message: "Notes/Link not found." });
    }

    if (user?.role !== "ADMIN" && existingNote.institutionId !== user?.institutionId) {
      return res.status(403).json({ success: false, message: "Forbidden: You can only edit your own institution's notes." });
    }

    let filesArray = (Array.isArray(existingNote.fileUrl) ? existingNote.fileUrl : []) as any[];
    
    let existingFiles: any[] = [];
    let existingFilesProvided = false;
    if (req.body.existingFiles !== undefined) {
      existingFilesProvided = true;
      try {
        const parsed = typeof req.body.existingFiles === "string" ? JSON.parse(req.body.existingFiles) : req.body.existingFiles;
        if (Array.isArray(parsed)) {
            existingFiles = parsed;
        }
      } catch (e) {
         // ignore
      }
    }

    if (req.files && Array.isArray(req.files) && req.files.length > 0) {
      const newFiles = req.files.map((file: any) => ({
        name: file.originalname || file.filename,
        url: toPublicPath(file.path) || undefined,
      }));
      filesArray = [...existingFiles, ...newFiles];
    } else if (existingFilesProvided) {
      filesArray = existingFiles;
    }

    let fileUrl: any = existingFilesProvided || (req.files && Array.isArray(req.files) && req.files.length > 0) ? filesArray : existingNote.fileUrl;

    const resolvedType: NotesType = (type ?? existingNote.type) as NotesType;

    const newVerification =
      resolvedType === NotesType.PDF
        ? NotesLinksStatus.VERIFIED
        : (user?.role === "ADMIN" ? existingNote.verification : NotesLinksStatus.PENDING);

    let parsedTopics = undefined;
    if (topics !== undefined) {
      try {
        parsedTopics = typeof topics === "string" ? JSON.parse(topics) : topics;
      } catch (e) {
        parsedTopics = topics;
      }
    }

    const updatedNotesLink = await prisma.notesLinks.update({
      where: { id },
      data: {
        title: title ?? existingNote.title,
        type: resolvedType,
        fileUrl: resolvedType === NotesType.PDF ? fileUrl : null,
        linkUrl: resolvedType === NotesType.LINK ? (linkUrl ?? existingNote.linkUrl) : null,
        topics: parsedTopics !== undefined ? parsedTopics : existingNote.topics,
        subjectId: subjectId ?? existingNote.subjectId,
        verification: newVerification,
        rejectReason: null,
      },
    });

    const parsedFiles = (Array.isArray(updatedNotesLink.fileUrl) ? updatedNotesLink.fileUrl : []) as any[];
    const formattedFiles = parsedFiles.map((f: any) => ({ ...f, url: formatFileUrl(f.url) }));

    return res.status(200).json({
      success: true,
      message:
        resolvedType === NotesType.PDF
          ? "Notes updated successfully."
          : "Link updated and sent for admin approval.",
      data: {
        ...updatedNotesLink,
        fileUrl: formattedFiles.length > 0 ? formattedFiles[0].url : null,
        files: formattedFiles,
      },
    });
  } catch (error: any) {
    console.error("Error updating notes/link:", error);
    return res.status(500).json({
      success: false,
      message: "Internal server error.",
      error: error.message,
    });
  }
};

// Delete notes and links
export const deleteNotesLink = async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const user = req.user;

    const existingNote = await prisma.notesLinks.findUnique({ where: { id } });
    if (!existingNote) {
      return res.status(404).json({ success: false, message: "Notes/Link not found." });
    }

    if (user?.role !== "ADMIN" && existingNote.institutionId !== user?.institutionId) {
      return res.status(403).json({ success: false, message: "Forbidden: You can only delete your own institution's notes." });
    }

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

    const remainingNotes = await prisma.notesLinks.count({
      where: { subjectId: existingNote.subjectId }
    });

    if (remainingNotes === 0) {
      await prisma.subjectsNotesToExam.deleteMany({
        where: { subjectId: existingNote.subjectId }
      });
    }

    return res.status(200).json({
      success: true,
      message: "Notes/Link deleted successfully.",
    });
  } catch (error: any) {
    console.error("Error deleting notes/link:", error);
    return res.status(500).json({
      success: false,
      message: "Internal server error.",
      error: error.message,
    });
  }
};

// Report for notes / link from student
export const reportNotesLink = async (req: Request, res: Response) => {
  try {
    const { id: notesLinksId } = req.params;
    const user = req.user;
    const { message, studentInstitutionId } = req.body;

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

    const studentRecord = await prisma.student.findFirst({
      where: { userId: user?.id },
    });

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

    const notesLink = await prisma.notesLinks.findUnique({ where: { id: notesLinksId } });
    if (!notesLink || notesLink.verification === NotesLinksStatus.REJECTED) {
      return res.status(404).json({ success: false, message: "Notes/Link not found or not accessible." });
    }

    const report = await prisma.notesLinksReport.create({
      data: {
        message,
        notesLinksId,
        studentId: studentRecord.id,
        studentInstitutionId: studentInstitutionId || null,
      },
    });

    const parsedFiles = (Array.isArray(notesLink.fileUrl) ? notesLink.fileUrl : []) as any[];
    const formattedFiles = parsedFiles.map((f: any) => ({ ...f, url: formatFileUrl(f.url) }));

    return res.status(201).json({
      success: true,
      message: "Report submitted successfully.",
      data: {
        ...notesLink,
        fileUrl: formattedFiles.length > 0 ? formattedFiles[0].url : null,
        files: formattedFiles,
      },
    });
  } catch (error: any) {
    console.error("Error submitting report:", error);
    return res.status(500).json({
      success: false,
      message: "Internal server error.",
      error: error.message,
    });
  }
};