import express from "express";
import fs from "fs";
import path from "path";
import crypto from "crypto";
import multer from "multer";

import { requireAuth } from "../../middleware/auth.js";
import { validate } from "../../shared/validate.js";
import { httpError } from "../../shared/errors.js";
import { Channel, Message } from "./chat.model.js";
import { User } from "../users/user.model.js";
import { createAndSendNotifications } from "../notifications/notification.service.js";
import {
  createChannelSchema,
  updateChannelSchema,
  postMessageSchema,
} from "./chat.schemas.js";

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

const uploadsDir = path.join(process.cwd(), "uploads");
fs.mkdirSync(uploadsDir, { recursive: true });

const upload = multer({
  storage: multer.diskStorage({
    destination: (_req, _file, cb) => cb(null, uploadsDir),
    filename: (_req, file, cb) => {
      const original = String(file.originalname || "file");
      const ext = path.extname(original).slice(0, 10);
      const safeExt = ext && ext.startsWith(".") ? ext : "";
      const name = `${Date.now()}-${crypto
        .randomBytes(6)
        .toString("hex")}${safeExt}`;
      cb(null, name);
    },
  }),
  limits: {
    fileSize: 10 * 1024 * 1024, // 10MB
  },
});

// Upload attachment (image/file) for chat messages
chatRouter.post("/uploads", upload.single("file"), async (req, res, next) => {
  try {
    if (!req.file) throw httpError(400, "No file uploaded");

    const mimeType = req.file.mimetype || "application/octet-stream";
    const type = mimeType.startsWith("image/") ? "image" : "file";
    const url = `/uploads/${req.file.filename}`;

    res.status(201).json({
      url,
      filename: req.file.originalname,
      mimeType,
      type,
      size: req.file.size,
    });
  } catch (err) {
    next(err);
  }
});

chatRouter.get("/channels", async (req, res, next) => {
  try {
    const userRole = req.user.role;
    const userId = req.user._id;

    // Admins see all channels
    if (req.user.role === "admin") {
      const channels = await Channel.find({})
        .populate("createdBy", "fullName role")
        .populate("members", "fullName email role")
        .sort({ isPinned: -1, lastMessageAt: -1, createdAt: -1 })
        .lean();
      return res.json({ channels });
    }

    const isInternOrStudent = userRole === "intern" || userRole === "student";

    // Customers only see private channels they belong to (support/ticket chats).
    if (userRole === "customer") {
      const channels = await Channel.find({ members: userId })
        .populate("createdBy", "fullName role")
        .populate("members", "fullName email role")
        .sort({ isPinned: -1, lastMessageAt: -1, createdAt: -1 })
        .lean();
      return res.json({ channels });
    }

    // For non-admins:
    // - Always include channels where they are members.
    // - For interns/students, only include non-private channels explicitly allowing their role.
    // - For employees, keep broader access (role match OR no role restrictions).
    const query = {
      $or: [
        { members: userId },
        isInternOrStudent
          ? {
              isPrivate: false,
              allowedRoles: { $in: [userRole] },
            }
          : {
              isPrivate: false,
              $or: [
                { allowedRoles: { $in: [userRole] } },
                { allowedRoles: { $exists: false } },
                { allowedRoles: { $size: 0 } },
              ],
            },
      ],
    };

    const channels = await Channel.find(query)
      .populate("createdBy", "fullName role")
      .populate("members", "fullName email role")
      .sort({ isPinned: -1, lastMessageAt: -1, createdAt: -1 })
      .lean();
    res.json({ channels });
  } catch (err) {
    next(err);
  }
});

chatRouter.post(
  "/channels",
  validate(createChannelSchema),
  async (req, res, next) => {
    try {
      if (req.user.role === "customer") {
        throw httpError(403, "Customers cannot create channels");
      }

      const {
        name,
        description,
        type,
        groupType,
        allowedRoles,
        category,
        isPrivate,
        memberIds,
      } = req.validated.body;

      const isAdmin = req.user.role === "admin";
      const isInternOrStudent =
        req.user.role === "intern" || req.user.role === "student";

      // Interns/Students: cannot create channels that include/affect other users.
      // They may only create private channels for themselves (no memberIds, no role/group settings).
      if (isInternOrStudent) {
        if (
          type === "group" ||
          groupType ||
          allowedRoles ||
          (memberIds && memberIds.length > 0)
        ) {
          throw httpError(
            403,
            "Only admins can create group/role-based channels"
          );
        }
        if (!isPrivate) {
          throw httpError(403, "Only admins can create public channels");
        }
      }

      // Non-admins cannot create group chats
      if (type === "group" && !isAdmin) {
        throw httpError(403, "Only admins can create group chats");
      }

      // Only admins can create announcement channels
      if (type === "announcement" && req.user.role !== "admin") {
        throw httpError(403, "Only admins can create announcement channels");
      }

      // Only admins can set custom membership on creation
      if (!isAdmin && memberIds && memberIds.length > 0) {
        throw httpError(403, "Only admins can add members to channels");
      }

      // Set allowed roles based on group type
      let finalAllowedRoles = allowedRoles;
      if (groupType === "employee-only") {
        finalAllowedRoles = ["admin", "employee"];
      } else if (groupType === "employee-intern") {
        finalAllowedRoles = ["admin", "employee", "intern"];
      } else if (groupType === "employee-student") {
        finalAllowedRoles = ["admin", "employee", "student"];
      } else if (groupType === "all-staff") {
        finalAllowedRoles = ["admin", "employee", "intern", "student"];
      }

      const channel = await Channel.create({
        name,
        description,
        type: type || "general",
        groupType,
        allowedRoles: finalAllowedRoles,
        category,
        isPrivate: isInternOrStudent ? true : isPrivate,
        members: (isInternOrStudent ? true : isPrivate)
          ? Array.from(
              new Set([
                req.user._id.toString(),
                ...(isAdmin ? memberIds || [] : []),
              ])
            )
          : [],
        createdBy: req.user._id,
      });

      const populated = await Channel.findById(channel._id)
        .populate("createdBy", "fullName role")
        .populate("members", "fullName email role")
        .lean();

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

chatRouter.patch(
  "/channels/:channelId",
  validate(updateChannelSchema),
  async (req, res, next) => {
    try {
      const { channelId } = req.validated.params;
      const { name, description, memberIds, isPinned } = req.validated.body;

      const channel = await Channel.findById(channelId);
      if (!channel) throw httpError(404, "Channel not found");

      const isAdmin = req.user.role === "admin";
      const isInternStudentGroup =
        channel.groupType === "employee-intern" ||
        channel.groupType === "employee-student" ||
        channel.groupType === "all-staff" ||
        (Array.isArray(channel.allowedRoles) &&
          (channel.allowedRoles.includes("intern") ||
            channel.allowedRoles.includes("student")));

      // Only admin or creator can edit
      const canEdit =
        req.user.role === "admin" ||
        channel.createdBy.toString() === req.user._id.toString();
      if (!canEdit) throw httpError(403, "Forbidden");

      // For intern/student related group channels, only admins can modify.
      if (!isAdmin && isInternStudentGroup) {
        throw httpError(403, "Only admins can modify this channel");
      }

      // Only admins can change channel members
      if (memberIds !== undefined && !isAdmin) {
        throw httpError(403, "Only admins can add/remove channel members");
      }

      if (name !== undefined) channel.name = name;
      if (description !== undefined) channel.description = description;
      if (memberIds !== undefined) channel.members = memberIds;
      if (isPinned !== undefined) channel.isPinned = isPinned;

      await channel.save();

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

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

chatRouter.get("/channels/:channelId/messages", async (req, res, next) => {
  try {
    const { channelId } = req.params;
    const { limit = 50, before } = req.query;

    const channel = await Channel.findById(channelId).lean();
    if (!channel) throw httpError(404, "Channel not found");

    // Check access permissions
    const isAdmin = req.user.role === "admin";
    const isMember = channel.members?.some(
      (m) => m.toString() === req.user._id.toString()
    );

    // For private channels, user must be a member
    if (channel.isPrivate && !isMember && !isAdmin) {
      throw httpError(403, "Forbidden");
    }

    // For channels with allowedRoles, check if user's role is allowed
    if (!isAdmin && channel.allowedRoles && channel.allowedRoles.length > 0) {
      if (!channel.allowedRoles.includes(req.user.role)) {
        throw httpError(403, "Forbidden");
      }
    }

    const query = { channelId };
    if (before) {
      query.createdAt = { $lt: new Date(before) };
    }

    const messages = await Message.find(query)
      .populate("senderId", "fullName email role")
      .populate("replyTo", "text senderId")
      .sort({ createdAt: -1 })
      .limit(parseInt(limit))
      .lean();

    // Transform messages to include user info in the expected format
    const transformedMessages = messages.map((msg) => ({
      ...msg,
      user: msg.senderId,
    }));

    res.json({ messages: transformedMessages.reverse() });
  } catch (err) {
    next(err);
  }
});

chatRouter.post(
  "/channels/:channelId/messages",
  validate(postMessageSchema),
  async (req, res, next) => {
    try {
      const { channelId } = req.validated.params;
      const { text, replyTo, attachments } = req.validated.body;

      const channel = await Channel.findById(channelId).lean();
      if (!channel) throw httpError(404, "Channel not found");

      // Check posting permissions
      const isAdmin = req.user.role === "admin";
      const isMember = channel.members?.some(
        (m) => m.toString() === req.user._id.toString()
      );

      // For private channels, user must be a member
      if (channel.isPrivate && !isMember && !isAdmin) {
        throw httpError(403, "Forbidden");
      }

      // For channels with allowedRoles, check if user's role is allowed
      if (!isAdmin && channel.allowedRoles && channel.allowedRoles.length > 0) {
        if (!channel.allowedRoles.includes(req.user.role)) {
          throw httpError(403, "Forbidden");
        }
      }

      // For announcement channels, only admins can post
      if (channel.type === "announcement" && req.user.role !== "admin") {
        throw httpError(403, "Only admins can post in announcement channels");
      }

      const message = await Message.create({
        channelId,
        senderId: req.user._id,
        text:
          typeof text === "string" && text.trim().length > 0
            ? text.trim()
            : undefined,
        replyTo: replyTo || undefined,
        attachments: Array.isArray(attachments) ? attachments : undefined,
      });

      // Update last message timestamp
      await Channel.findByIdAndUpdate(channelId, {
        lastMessageAt: new Date(),
      });

      const populated = await Message.findById(message._id)
        .populate("senderId", "fullName email role")
        .populate("replyTo", "text senderId")
        .lean();

      // Notify recipients (in-app + push)
      try {
        let recipientIds = [];

        if (Array.isArray(channel.members) && channel.members.length > 0) {
          recipientIds = channel.members.map((m) => m.toString());
        } else if (
          Array.isArray(channel.allowedRoles) &&
          channel.allowedRoles.length > 0
        ) {
          const users = await User.find(
            { status: "active", role: { $in: channel.allowedRoles } },
            "_id"
          ).lean();
          recipientIds = users.map((u) => u._id.toString());
        } else {
          // Default: notify all active staff (exclude customers)
          const users = await User.find(
            {
              status: "active",
              role: { $in: ["admin", "employee", "intern", "student"] },
            },
            "_id"
          ).lean();
          recipientIds = users.map((u) => u._id.toString());
        }

        const senderId = req.user._id.toString();
        recipientIds = recipientIds.filter((id) => id !== senderId);

        const previewText =
          typeof populated?.text === "string" &&
          populated.text.trim().length > 0
            ? populated.text.trim().slice(0, 140)
            : Array.isArray(populated?.attachments) &&
              populated.attachments.length > 0
            ? "Attachment"
            : "New message";

        if (recipientIds.length > 0) {
          await createAndSendNotifications({
            userIds: recipientIds,
            type: "chat",
            title: `New message: ${channel.name}`,
            body: previewText,
            data: { kind: "chat", channelId, messageId: message._id },
            createdBy: req.user._id,
          });
        }
      } catch {
        // ignore
      }

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

// Delete a channel (admin or creator only)
chatRouter.delete("/channels/:channelId", async (req, res, next) => {
  try {
    const { channelId } = req.params;

    const channel = await Channel.findById(channelId);
    if (!channel) throw httpError(404, "Channel not found");

    const canDelete =
      req.user.role === "admin" ||
      channel.createdBy.toString() === req.user._id.toString();
    if (!canDelete) throw httpError(403, "Forbidden");

    // Delete all messages in the channel
    await Message.deleteMany({ channelId });
    await Channel.findByIdAndDelete(channelId);

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