import express from "express";

import { requireAuth } from "../../middleware/auth.js";
import { validate } from "../../shared/validate.js";
import { httpError } from "../../shared/errors.js";
import { Project, ProjectReport, UserProjectStatus } from "./project.model.js";
import { Channel } from "../chat/chat.model.js";
import { User } from "../users/user.model.js";
import { createAndSendNotifications } from "../notifications/notification.service.js";
import {
  createProjectSchema,
  updateProjectSchema,
  createReportSchema,
  reviewReportSchema,
} from "./projects.schemas.js";

export const projectsRouter = express.Router();
projectsRouter.use(requireAuth);

projectsRouter.use((req, _res, next) => {
  if (req.user?.role === "customer") {
    return next(httpError(403, "Customers cannot access projects"));
  }
  return next();
});

projectsRouter.get("/", async (req, res, next) => {
  try {
    let query = {};

    if (req.user.role === "admin") {
      // Admins see all projects
      query = {};
    } else if (req.user.role === "student" || req.user.role === "intern") {
      // Students/Interns only see projects they are explicitly assigned to
      // Interns only see projects they are explicitly assigned to
      query = { members: req.user._id };
    } else {
      // Employees see projects in their category or where they are members
      // Also include projects with "general" category as a fallback
      const userCategory = req.user.category || "general";

      // Debug logging
      console.log(
        `[Project Filter] User: ${req.user.fullName}, Category: ${userCategory}, Role: ${req.user.role}`
      );

      query = { $or: [{ category: userCategory }, { members: req.user._id }] };
    }

    const projects = await Project.find(query)
      .populate("createdBy", "fullName email")
      .populate("members", "fullName email role category")
      .populate("channelId", "name")
      .sort({ createdAt: -1 })
      .lean();

    // Debug logging
    if (req.user.role !== "admin") {
      console.log(
        `[Project Filter] Found ${projects.length} projects for user`
      );
      projects.forEach((p) => {
        console.log(
          `  - ${p.name} (category: ${p.category}, members: ${
            p.members?.length || 0
          })`
        );
      });
    }

    res.json({ projects });
  } catch (err) {
    next(err);
  }
});

projectsRouter.post(
  "/",
  validate(createProjectSchema),
  async (req, res, next) => {
    try {
      // Only admins can create projects
      if (req.user.role !== "admin") {
        throw httpError(403, "Only administrators can create projects");
      }

      const {
        name,
        description,
        category,
        memberIds,
        priority,
        deadline,
        startDate,
        tags,
      } = req.validated.body;

      // Use provided category or default to "general"
      const projectCategory = category || "general";

      // Ensure memberIds is an array and filter out any invalid entries
      const validMemberIds = Array.isArray(memberIds)
        ? memberIds.filter((id) => id)
        : [];

      // Create a dedicated channel for this project
      const channel = await Channel.create({
        name: `${name} - Discussion`,
        isPrivate: true,
        members: [req.user._id.toString(), ...validMemberIds],
        createdBy: req.user._id,
      });

      const project = await Project.create({
        name,
        description,
        category: projectCategory,
        createdBy: req.user._id,
        members: validMemberIds,
        priority: priority || "medium",
        deadline: deadline ? new Date(deadline) : undefined,
        startDate: startDate ? new Date(startDate) : undefined,
        tags,
        channelId: channel._id,
      });

      const populated = await Project.findById(project._id)
        .populate("createdBy", "fullName email")
        .populate("members", "fullName email role category")
        .populate("channelId", "name")
        .lean();

      // Notify assigned members
      try {
        if (validMemberIds.length > 0) {
          await createAndSendNotifications({
            userIds: validMemberIds,
            type: "project",
            title: `Assigned to project: ${name}`,
            body: description ? String(description).slice(0, 140) : undefined,
            data: { kind: "project", projectId: project._id },
            createdBy: req.user._id,
          });
        }
      } catch {
        // ignore
      }

      res.status(201).json({ project: populated });
    } catch (err) {
      next(err);
    }
  }
);

projectsRouter.patch(
  "/:id",
  validate(updateProjectSchema),
  async (req, res, next) => {
    try {
      const { id } = req.validated.params;
      const {
        name,
        description,
        category,
        status,
        priority,
        memberIds,
        deadline,
        startDate,
        progress,
        tags,
      } = req.validated.body;

      const project = await Project.findById(id);
      if (!project) throw httpError(404, "Project not found");

      const prevMembers = Array.isArray(project.members)
        ? project.members.map((m) => m.toString())
        : [];

      // Only admins can edit projects
      const canEdit = req.user.role === "admin";
      if (!canEdit)
        throw httpError(403, "Only administrators can edit projects");

      if (name !== undefined) project.name = name;
      if (description !== undefined) project.description = description;
      if (category !== undefined) project.category = category;
      if (status !== undefined) project.status = status;
      if (priority !== undefined) project.priority = priority;
      if (memberIds !== undefined) {
        project.members = memberIds;
        // Update channel members too
        if (project.channelId) {
          await Channel.findByIdAndUpdate(project.channelId, {
            $set: { members: [project.createdBy, ...memberIds] },
          });
        }
      }
      if (deadline !== undefined)
        project.deadline = deadline ? new Date(deadline) : null;
      if (startDate !== undefined)
        project.startDate = startDate ? new Date(startDate) : null;
      if (progress !== undefined) project.progress = progress;
      if (tags !== undefined) project.tags = tags;

      await project.save();

      // Notify members about updates / new assignments
      try {
        const nextMembers = Array.isArray(project.members)
          ? project.members.map((m) => m.toString())
          : [];
        const newlyAdded = nextMembers.filter((m) => !prevMembers.includes(m));
        const notifyMembers = Array.from(new Set(nextMembers));

        if (newlyAdded.length > 0) {
          await createAndSendNotifications({
            userIds: newlyAdded,
            type: "project",
            title: `Added to project: ${project.name}`,
            body: "You have been added to a project.",
            data: { kind: "project", projectId: project._id },
            createdBy: req.user._id,
          });
        }

        if (notifyMembers.length > 0) {
          await createAndSendNotifications({
            userIds: notifyMembers,
            type: "project",
            title: `Project updated: ${project.name}`,
            body: "A project you are assigned to was updated.",
            data: { kind: "project", projectId: project._id },
            createdBy: req.user._id,
          });
        }
      } catch {
        // ignore
      }

      const populated = await Project.findById(id)
        .populate("createdBy", "fullName email")
        .populate("members", "fullName email role")
        .populate("channelId", "name")
        .lean();

      res.json({ project: populated });
    } catch (err) {
      next(err);
    }
  }
);

// Submit a report for a project
projectsRouter.post(
  "/:projectId/reports",
  validate(createReportSchema),
  async (req, res, next) => {
    try {
      const { projectId } = req.validated.params;
      const { title, description, hoursSpent, completionPercentage } =
        req.validated.body;

      const project = await Project.findById(projectId);
      if (!project) throw httpError(404, "Project not found");

      // Check if user has access
      const userCategory = req.user.category || "general";
      const isMember = project.members.some(
        (m) => m.toString() === req.user._id.toString()
      );
      const hasAccess =
        req.user.role === "admin" ||
        (req.user.role === "student" && isMember) ||
        (req.user.role === "intern" && isMember) ||
        (req.user.role === "employee" &&
          (isMember || project.category === userCategory));
      if (!hasAccess)
        throw httpError(403, "You do not have access to this project");

      const report = await ProjectReport.create({
        projectId,
        userId: req.user._id,
        title,
        description,
        hoursSpent,
        completionPercentage,
        status: "submitted",
      });

      // Notify admins about submitted report
      try {
        const admins = await User.find(
          { role: "admin", status: "active" },
          "_id"
        )
          .lean()
          .catch(() => []);
        if (admins.length > 0) {
          await createAndSendNotifications({
            userIds: admins.map((u) => u._id),
            type: "activity",
            title: `Project report submitted: ${title}`,
            body:
              typeof description === "string"
                ? description.slice(0, 140)
                : undefined,
            data: { kind: "project-report", projectId, reportId: report._id },
            createdBy: req.user._id,
          });
        }
      } catch {
        // ignore
      }

      const populated = await ProjectReport.findById(report._id)
        .populate("userId", "fullName email role")
        .populate("projectId", "name")
        .lean();

      res.status(201).json({ report: populated });
    } catch (err) {
      next(err);
    }
  }
);

// Get reports for a project
projectsRouter.get("/:projectId/reports", async (req, res, next) => {
  try {
    const { projectId } = req.params;

    const project = await Project.findById(projectId);
    if (!project) throw httpError(404, "Project not found");

    // Check if user has access
    const userCategory = req.user.category || "general";
    const isMember = project.members.some(
      (m) => m.toString() === req.user._id.toString()
    );
    const hasAccess =
      req.user.role === "admin" ||
      (req.user.role === "student" && isMember) ||
      (req.user.role === "intern" && isMember) ||
      (req.user.role === "employee" &&
        (isMember || project.category === userCategory));
    if (!hasAccess) throw httpError(403, "Access denied");

    const reports = await ProjectReport.find({ projectId })
      .populate("userId", "fullName email role")
      .populate("reviewedBy", "fullName")
      .sort({ createdAt: -1 })
      .lean();

    res.json({ reports });
  } catch (err) {
    next(err);
  }
});

// Admin: Review a report
projectsRouter.patch(
  "/reports/:reportId/review",
  validate(reviewReportSchema),
  async (req, res, next) => {
    try {
      if (req.user.role !== "admin") {
        throw httpError(403, "Admin access required");
      }

      const { reportId } = req.validated.params;
      const { status, reviewNote } = req.validated.body;

      const report = await ProjectReport.findById(reportId);
      if (!report) throw httpError(404, "Report not found");

      report.status = status;
      report.reviewedBy = req.user._id;
      report.reviewedAt = new Date();
      if (reviewNote) report.reviewNote = reviewNote;

      await report.save();

      // Notify the report owner about the review
      try {
        await createAndSendNotifications({
          userIds: [report.userId],
          type: "activity",
          title: `Report ${status}: ${reportId}`,
          body: reviewNote
            ? String(reviewNote).slice(0, 140)
            : "Your project report was reviewed.",
          data: { kind: "project-report-review", reportId, status },
          createdBy: req.user._id,
        });
      } catch {
        // ignore
      }

      const populated = await ProjectReport.findById(reportId)
        .populate("userId", "fullName email role")
        .populate("reviewedBy", "fullName")
        .populate("projectId", "name")
        .lean();

      res.json({ report: populated });
    } catch (err) {
      next(err);
    }
  }
);

// Get all reports (admin only)
projectsRouter.get("/reports/all", async (req, res, next) => {
  try {
    if (req.user.role !== "admin") {
      throw httpError(403, "Admin access required");
    }

    const reports = await ProjectReport.find()
      .populate("userId", "fullName email role")
      .populate("projectId", "name priority status")
      .populate("reviewedBy", "fullName")
      .sort({ createdAt: -1 })
      .lean();

    res.json({ reports });
  } catch (err) {
    next(err);
  }
});

// Delete a project (admin only)
projectsRouter.delete("/:id", async (req, res, next) => {
  try {
    if (req.user.role !== "admin") {
      throw httpError(403, "Admin access required");
    }

    const { id } = req.params;
    const project = await Project.findById(id);

    if (!project) {
      throw httpError(404, "Project not found");
    }

    // Delete associated channel if exists
    if (project.channelId) {
      await Channel.findByIdAndDelete(project.channelId);
    }

    // Delete associated reports
    await ProjectReport.deleteMany({ projectId: id });

    // Delete associated user statuses
    await UserProjectStatus.deleteMany({ projectId: id });

    // Delete the project
    await Project.findByIdAndDelete(id);

    res.json({ message: "Project deleted successfully" });
  } catch (err) {
    next(err);
  }
});

// Update user status for a project
projectsRouter.post("/:id/user-status", async (req, res, next) => {
  try {
    const { id } = req.params;
    const { status, note, userId } = req.body;

    const project = await Project.findById(id);
    if (!project) throw httpError(404, "Project not found");

    // Validate status
    const validStatuses = ["open", "in-progress", "revise", "close"];
    if (!validStatuses.includes(status)) {
      throw httpError(
        400,
        "Invalid status. Must be one of: open, in-progress, revise, close"
      );
    }

    // Determine which user's status to update
    let targetUserId;
    if (req.user.role === "admin" && userId) {
      // Admin can update any user's status
      targetUserId = userId;
    } else {
      // Non-admin can only update their own status
      targetUserId = req.user._id;

      // Verify user is a member of the project
      const isMember = project.members.some(
        (m) => m.toString() === req.user._id.toString()
      );
      if (!isMember) {
        throw httpError(403, "You are not a member of this project");
      }
    }

    // Create or update the user status
    const userStatus = await UserProjectStatus.findOneAndUpdate(
      { projectId: id, userId: targetUserId },
      {
        status,
        note: note || undefined,
        updatedBy: req.user._id,
      },
      { upsert: true, new: true }
    )
      .populate("userId", "fullName email role")
      .populate("updatedBy", "fullName");

    // Notify admin about status update
    try {
      const admins = await User.find({ role: "admin" }).select("_id");
      const adminIds = admins.map((a) => a._id.toString());

      if (adminIds.length > 0 && req.user.role !== "admin") {
        await createAndSendNotifications({
          userIds: adminIds,
          type: "project",
          title: `Project Status Update: ${project.name}`,
          body: `${req.user.fullName} updated status to "${status}"`,
          data: { kind: "project", projectId: project._id },
          createdBy: req.user._id,
        });
      }

      // If admin updated someone's status, notify that user
      if (
        req.user.role === "admin" &&
        userId &&
        userId !== req.user._id.toString()
      ) {
        await createAndSendNotifications({
          userIds: [userId],
          type: "project",
          title: `Status Updated: ${project.name}`,
          body: `Admin updated your status to "${status}"`,
          data: { kind: "project", projectId: project._id },
          createdBy: req.user._id,
        });
      }
    } catch {
      // ignore notification errors
    }

    res.json({ userStatus });
  } catch (err) {
    next(err);
  }
});

// Get user statuses for a project
projectsRouter.get("/:id/user-status", async (req, res, next) => {
  try {
    const { id } = req.params;

    const project = await Project.findById(id);
    if (!project) throw httpError(404, "Project not found");

    // Get all user statuses for this project
    const statuses = await UserProjectStatus.find({ projectId: id })
      .populate("userId", "fullName email role")
      .populate("updatedBy", "fullName")
      .sort({ updatedAt: -1 })
      .lean();

    res.json({ statuses });
  } catch (err) {
    next(err);
  }
});

// Get current user's status for a project
projectsRouter.get("/:id/my-status", async (req, res, next) => {
  try {
    const { id } = req.params;

    const project = await Project.findById(id);
    if (!project) throw httpError(404, "Project not found");

    const userStatus = await UserProjectStatus.findOne({
      projectId: id,
      userId: req.user._id,
    })
      .populate("userId", "fullName email role")
      .populate("updatedBy", "fullName")
      .lean();

    res.json({ userStatus: userStatus || null });
  } catch (err) {
    next(err);
  }
});
