const prisma = require("../lib/prisma");
const path = require("path");
const fs = require("fs");
const { createCanvas, registerFont, loadImage } = require("canvas");
const fetch = require("node-fetch");
const { notifyUser, notifyManyUsers } = require("../services/notification.service");

// Register Roboto-Bold from your assets folder
registerFont(
  path.join(__dirname, "../assets/Roboto/static/Roboto-Bold.ttf"),
  { family: "Roboto" }
);

/**
 * Generate a thumbnail image with the lesson title
 * @param {string} title - The lesson title
 * @param {object} req - Express request object, used to generate full URL
 * @returns {string} - URL of the generated thumbnail
 */
async function generateTitleThumbnail(title, req) {
  try {
    // Canvas dimensions
    const width = 480;
    const height = 270;
    const canvas = createCanvas(width, height);
    const ctx = canvas.getContext("2d");

    // ===== Background Gradient =====
    const gradient = ctx.createLinearGradient(0, 0, width, height);
    gradient.addColorStop(0, "#1E88E5"); // dark blue
    gradient.addColorStop(1, "#42A5F5"); // light blue
    ctx.fillStyle = gradient;

    // Rounded rectangle background
    const radius = 20;
    ctx.beginPath();
    ctx.moveTo(radius, 0);
    ctx.lineTo(width - radius, 0);
    ctx.quadraticCurveTo(width, 0, width, radius);
    ctx.lineTo(width, height - radius);
    ctx.quadraticCurveTo(width, height, width - radius, height);
    ctx.lineTo(radius, height);
    ctx.quadraticCurveTo(0, height, 0, height - radius);
    ctx.lineTo(0, radius);
    ctx.quadraticCurveTo(0, 0, radius, 0);
    ctx.closePath();
    ctx.fill();

    // ===== Title Text =====
    ctx.fillStyle = "#FFFFFF";
    ctx.font = "bold 32px Roboto"; // <-- Roboto-Bold
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";

    // Wrap text if too long
    const maxWidth = width - 40; // padding
    const lines = [];
    const words = title.split(" ");
    let line = "";

    for (let n = 0; n < words.length; n++) {
      const testLine = line + words[n] + " ";
      const metrics = ctx.measureText(testLine);
      if (metrics.width > maxWidth && n > 0) {
        lines.push(line.trim());
        line = words[n] + " ";
      } else {
        line = testLine;
      }
    }
    lines.push(line.trim());

    // Draw each line
    const lineHeight = 40;
    const startY = height / 2 - ((lines.length - 1) / 2) * lineHeight;
    lines.forEach((l, i) => {
      ctx.fillText(l, width / 2, startY + i * lineHeight);
    });

    // ===== Save File =====
    const dir = path.join(__dirname, "../uploads/thumbnails");
    if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });

    const fileName = `lesson_${Date.now()}.png`;
    const filePath = path.join(dir, fileName);

    const buffer = canvas.toBuffer("image/png");
    fs.writeFileSync(filePath, buffer);

    // Return full URL dynamically
    return `/uploads/thumbnails/${fileName}`;
  } catch (err) {
    console.error("Thumbnail generation error:", err);
    // fallback placeholder
    return `/thumbnails/lesson-placeholder.png`;
  }
}

// Helper to get YouTube video duration in seconds
async function getYouTubeVideoDuration(videoUrl) {
  if (!videoUrl) return 0;

  const videoIdMatch = videoUrl.match(/(?:v=|\/)([a-zA-Z0-9_-]{11})/);
  if (!videoIdMatch) return 0;
  const videoId = videoIdMatch[1];

  const apiKey = process.env.YOUTUBE_API_KEY;
  const url = `https://www.googleapis.com/youtube/v3/videos?id=${videoId}&part=contentDetails&key=${apiKey}`;

  const res = await fetch(url);
  if (!res.ok) return 0;

  const data = await res.json();
  if (!data.items || !data.items[0]) return 0;

  const durationISO = data.items[0].contentDetails.duration; // e.g., PT5M30S
  return parseISODuration(durationISO); // convert to seconds
}

// Convert ISO 8601 duration to seconds
function parseISODuration(duration) {
  const match = duration.match(/PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/);
  if (!match) return 0;
  const hours = parseInt(match[1] || 0);
  const minutes = parseInt(match[2] || 0);
  const seconds = parseInt(match[3] || 0);
  return hours * 3600 + minutes * 60 + seconds;
}

// Controller
exports.createLesson = async (req, res) => {
  try {
    const { title, description, videoUrl, pdfUrl } = req.body;
    const { subjectId } = req.params;

    if (!title || !description)
      return res.status(400).json({ message: "Title and description required" });

    const parsedSubjectId = parseInt(subjectId);
    if (isNaN(parsedSubjectId))
      return res.status(400).json({ message: "Invalid subject ID" });

    const subject = await prisma.subject.findUnique({ where: { id: parsedSubjectId } });
    if (!subject) return res.status(404).json({ message: "Subject not found" });

    if (subject.teacherId !== req.user.id)
      return res.status(403).json({ message: "Not assigned to this subject" });

    // Generate thumbnail here if needed (your existing function)
    const thumbnailUrl = await generateTitleThumbnail(title, req);

    // Get video duration
    const videoDurationSeconds = await getYouTubeVideoDuration(videoUrl);

    const lesson = await prisma.lesson.create({
      data: {
        title,
        description,
        videoUrl: videoUrl || null,
        pdfUrl: pdfUrl || null,
        thumbnailUrl,
        videoDurationSeconds,
        subjectId: parsedSubjectId,
        teacherId: req.user.id,
      },
    });

    // 🔔 Notify all enrolled students
    const enrolledStudents = await prisma.user.findMany({
      where: {
        role: "student",
        enrollment: {
          some: { subjectId: parsedSubjectId },
        },
      },
      select: { id: true },
    });

    const studentIds = enrolledStudents.map((s) => s.id);

    if (studentIds.length > 0) {
      const message = `New lesson "${title}" has been added to your course "${subject.title}".`;
      await notifyManyUsers(req, studentIds, message, null); // senderId = null for system
    }

    res.status(200).json({ message: "Lesson created", lesson });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: "Server error" });
  }
};

//get lessons of a subject
exports.getSubjectLessons = async (req, res) => {
  try {
    const subjectId = parseInt(req.params.subjectId);
    const page = Number(req.query.page) || 0;
    const pageSize = Number(req.query.pageSize) || 10;

    // ===== Sorting =====
    let orderBy = [];
    if (req.query.sort) {
      try {
        const sortModel = JSON.parse(req.query.sort);
        orderBy = sortModel
          .filter(({ field }) =>
            ["id", "title", "description", "sno", "createdAt"].includes(field)
          )
          .map(({ field, sort }) => {
            if (field === "sno") return { id: sort.toLowerCase() }; // sort by id for sno
            return { [field]: sort.toLowerCase() };
          });
      } catch (err) {
        console.error("Invalid sort JSON", err);
      }
    }
    if (orderBy.length === 0) orderBy = [{ id: "asc" }];

    // ===== Filtering =====
    const where = { subjectId };
    if (req.query.filter) {
      try {
        const filterModel = JSON.parse(req.query.filter);
        const AND = [];

        filterModel.items?.forEach(({ field, operator, value }) => {
          if (!value) return;

          switch (field) {
            case "title":
            case "description":
              if (operator === "contains") {
                AND.push({ [field]: { contains: value, mode: "insensitive" } });
              }
              break;
            case "sno":
              if (!isNaN(Number(value))) {
                const snoNum = Number(value);
                AND.push({ id: snoNum }); // filter by id for sno
              }
              break;
          }
        });

        if (AND.length > 0) where.AND = AND;
      } catch (err) {
        console.error("Invalid filter JSON", err);
      }
    }

    // ===== Total count and paginated lessons =====
    const [lessons, total] = await Promise.all([
      prisma.lesson.findMany({
        where,
        skip: page * pageSize,
        take: pageSize,
        orderBy,
      }),
      prisma.lesson.count({ where }),
    ]);

    // ===== Format results =====
    const formatted = lessons.map((l, index) => {
      return {
        ...l,
        sno: page * pageSize + index + 1, // compute S.No
      };
    });

    res.json({ rows: formatted, total });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: "Server error" });
  }
};


//delete lesson (teacher or admin)
exports.deleteLesson = async (req, res) => {
  try {
    const lessonId = parseInt(req.params.id);

    const lesson = await prisma.lesson.findUnique({
      where: { id: lessonId },
      include: { subject: true },
    });

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

    //only admin or the teacher of that subject can delete
    if (req.user.role !== "admin" && req.user.id !== lesson.subject.teacherId) {
      return res.status(403).json({ message: "Not allowed" });
    }

    await prisma.lesson.delete({
      where: { id: lessonId },
    });
      
      return res.json({ message: "Lesson deleted successfully" });
  } catch (err) {
    console.log(err);
    return res.status(500).json({ message: "Server error" });
  }
};

// Get lesson by ID
exports.getLessonById = async (req, res) => {
  try {
    const lessonId = parseInt(req.params.id);
    if (isNaN(lessonId)) {
      return res.status(400).json({ message: "Invalid lesson ID" });
    }

    // ===== Sorting (optional, in case you want nested lists) =====
    let orderBy = [];
    if (req.query.sort) {
      try {
        const sortModel = JSON.parse(req.query.sort);
        orderBy = sortModel
          .filter(({ field }) =>
            ["id", "title", "description", "createdAt"].includes(field)
          )
          .map(({ field, sort }) => ({ [field]: sort.toLowerCase() }));
      } catch (err) {
        console.error("Invalid sort JSON", err);
      }
    }
    if (orderBy.length === 0) orderBy = [{ id: "asc" }];

    // ===== Fetch lesson with related data =====
    const lesson = await prisma.lesson.findUnique({
      where: { id: lessonId },
      include: {
        subject: true,
        teacher: true,
      },
    });

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

    // ===== Format lesson (optional: add S.No if needed) =====
    const formattedLesson = {
      ...lesson,
      sno: 1, // since it's a single lesson, S.No = 1
    };

    res.status(200).json(formattedLesson);
  } catch (err) {
    console.error("Get lesson by ID error:", err);
    res.status(500).json({ message: "Server error" });
  }
};

exports.updateLessonProgress = async (req, res) => {
  try {
    const lessonId = Number(req.params.lessonId);
    const studentId = req.user.id;

    const {
      videoWatchedSeconds,
      videoCompleted,
      assignmentCompleted,
      pdfViewed,
    } = req.body;

    const dataToUpdate = {};

    if (typeof videoWatchedSeconds === "number")
      dataToUpdate.videoWatchedSeconds = videoWatchedSeconds;

    if (typeof videoCompleted === "boolean")
      dataToUpdate.videoCompleted = videoCompleted;

    if (typeof assignmentCompleted === "boolean")
      dataToUpdate.assignmentCompleted = assignmentCompleted;

    if (typeof pdfViewed === "boolean")
      dataToUpdate.pdfViewed = pdfViewed;

    // completed should be derived, not blindly overwritten
    const existing = await prisma.lessonProgress.findUnique({
      where: { studentId_lessonId: { studentId, lessonId } },
    });

    const finalData = {
      ...existing,
      ...dataToUpdate,
    };

    const completed =
      finalData.videoCompleted &&
      finalData.assignmentCompleted &&
      finalData.pdfViewed;

    const progress = await prisma.lessonProgress.upsert({
      where: { studentId_lessonId: { studentId, lessonId } },
      update: {
        ...dataToUpdate,
        completed,
      },
      create: {
        studentId,
        lessonId,
        videoWatchedSeconds: videoWatchedSeconds ?? 0,
        videoCompleted: videoCompleted ?? false,
        assignmentCompleted: assignmentCompleted ?? false,
        pdfViewed: pdfViewed ?? false,
        completed,
      },
    });

    res.json({ message: "Progress updated", progress });
  } catch (err) {
    console.error(err);
    res.status(500).json({ message: "Server error" });
  }
};

exports.getLessonsWithProgress = async (req, res) => {
  try {
    const subjectId = parseInt(req.params.subjectId);
    const studentId = req.user.id;

    const page = Number(req.query.page) || 0;
    const pageSize = Number(req.query.pageSize) || 6;

    const total = await prisma.lesson.count({
      where: { subjectId },
    });

    // ✅ INCLUDE assignments count
    const lessons = await prisma.lesson.findMany({
      where: { subjectId },
      include: {
        assignments: { select: { id: true } },
      },
      orderBy: { id: "asc" },
      skip: page * pageSize,
      take: pageSize,
    });

    const progressList = await prisma.lessonProgress.findMany({
      where: {
        studentId,
        lessonId: { in: lessons.map((l) => l.id) },
      },
    });

    const lessonsWithProgress = lessons.map((lesson) => {
      const prog = progressList.find((p) => p.lessonId === lesson.id);

      // ===== Availability =====
      const hasVideo = !!lesson.videoUrl;
      const hasPdf = !!lesson.pdfUrl;
      const hasAssignment = lesson.assignments.length > 0;

      // ===== Progress state =====
      const videoWatchedSeconds = prog?.videoWatchedSeconds || 0;
      const videoDuration = lesson.videoDurationSeconds || 0;

      const videoPercent =
        hasVideo && videoDuration > 0
          ? Math.min((videoWatchedSeconds / videoDuration) * 100, 100)
          : 0;

      const videoCompleted = hasVideo ? prog?.videoCompleted || false : true;
      const assignmentCompleted = hasAssignment
        ? prog?.assignmentCompleted || false
        : true;
      const pdfViewed = hasPdf ? prog?.pdfViewed || false : true;

      // ===== Dynamic Weights =====
      let totalWeight = 0;
      const weights = {};

      if (hasVideo) {
        weights.video = 70;
        totalWeight += 70;
      }
      if (hasAssignment) {
        weights.assignment = 20;
        totalWeight += 20;
      }
      if (hasPdf) {
        weights.pdf = 10;
        totalWeight += 10;
      }

      // Normalize to 100
      Object.keys(weights).forEach((k) => {
        weights[k] = (weights[k] / totalWeight) * 100;
      });

      // ===== Final Progress =====
      const progress =
        (hasVideo ? videoPercent * (weights.video / 100) : 0) +
        (hasAssignment && assignmentCompleted ? weights.assignment : 0) +
        (hasPdf && pdfViewed ? weights.pdf : 0);

      const completed =
        videoCompleted && assignmentCompleted && pdfViewed;

      return {
        id: lesson.id,
        title: lesson.title,
        description: lesson.description,
        videoUrl: lesson.videoUrl,
        pdfUrl: lesson.pdfUrl,
        assignment: lesson.assignments,
        thumbnailUrl: lesson.thumbnailUrl,
        videoDurationSeconds: lesson.videoDurationSeconds,

        // ✅ Availability flags (frontend friendly)
        hasVideo,
        hasPdf,
        hasAssignment,

        // ✅ Progress info
        progress: Math.min(Math.round(progress), 100),
        videoPercent,
        videoWatchedSeconds,
        videoCompleted,
        assignmentCompleted,
        pdfViewed,
        completed,
      };
    });

    res.json({
      rows: lessonsWithProgress,
      total,
    });
  } catch (err) {
    console.error("getLessonsWithProgress error:", err);
    res.status(500).json({ message: "Server error" });
  }
};


