const { Router } = require("express");
const bcrypt = require("bcrypt");
const prisma = require("../prisma");
const { requireAuth } = require("../middleware/auth");
const {
  visibleUsersWhere,
  requireUserManager,
  assertCanAccessUser,
  assertCanAssignRoleIds,
} = require("../lib/rbac");

const router = Router();

const userSelect = {
  id: true,
  email: true,
  username: true,
  fullName: true,
  isVerified: true,
  isApproved: true,
  createdAt: true,
  updatedAt: true,
  roles: { select: { role: { select: { id: true, name: true } } } },
  branches: { select: { branch: { select: { id: true, name: true } } } },
  activeBranch: { select: { id: true, name: true } },
};

function formatUser(u) {
  return {
    id: u.id,
    email: u.email,
    username: u.username,
    fullName: u.fullName,
    isVerified: u.isVerified,
    isApproved: u.isApproved,
    createdAt: u.createdAt,
    updatedAt: u.updatedAt,
    roles: u.roles.map((r) => r.role),
    roleIds: u.roles.map((r) => r.role.id),
    branches: u.branches.map((b) => b.branch),
    branchIds: u.branches.map((b) => b.branch.id),
    activeBranch: u.activeBranch || null,
  };
}

router.get("/", requireAuth, requireUserManager, async (req, res, next) => {
  try {
    const page = Math.max(1, parseInt(req.query.page) || 1);
    const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 25));
    const skip = (page - 1) * limit;
    const search = (req.query.search || "").trim();

    const where = visibleUsersWhere(req);
    if (search) {
      where.OR = [
        { email: { contains: search, mode: "insensitive" } },
        { fullName: { contains: search, mode: "insensitive" } },
        { username: { contains: search, mode: "insensitive" } },
      ];
    }

    const [total, rows] = await Promise.all([
      prisma.user.count({ where }),
      prisma.user.findMany({
        where,
        orderBy: [{ createdAt: "desc" }],
        skip,
        take: limit,
        select: userSelect,
      }),
    ]);

    return res.json({
      data: rows.map(formatUser),
      pagination: { page, limit, total, totalPages: Math.ceil(total / limit) },
    });
  } catch (err) {
    return next(err);
  }
});

router.get("/:id", requireAuth, requireUserManager, async (req, res, next) => {
  try {
    const { id } = req.params;
    await assertCanAccessUser(prisma, req, id);
    const user = await prisma.user.findUnique({
      where: { id },
      select: userSelect,
    });
    if (!user) return res.status(404).json({ error: "User not found" });
    return res.json(formatUser(user));
  } catch (err) {
    if (err.status) return res.status(err.status).json({ error: err.message });
    return next(err);
  }
});

router.post("/", requireAuth, requireUserManager, async (req, res, next) => {
  try {
    const { email, fullName, username, password, roleIds, branchIds, isApproved } = req.body || {};

    if (!email || !fullName || !username || !password) {
      return res.status(400).json({ error: "email, fullName, username, and password are required" });
    }

    const uniqueRoleIds = Array.from(new Set((roleIds || []).map((x) => String(x).trim()).filter(Boolean)));
    const uniqueBranchIds = Array.from(new Set((branchIds || []).map((x) => String(x).trim()).filter(Boolean)));

    await assertCanAssignRoleIds(prisma, req, uniqueRoleIds);

    if (uniqueBranchIds.length) {
      const branches = await prisma.branch.findMany({ where: { id: { in: uniqueBranchIds } }, select: { id: true } });
      if (branches.length !== uniqueBranchIds.length) return res.status(400).json({ error: "One or more branches are invalid" });
    }

    const hashedPassword = await bcrypt.hash(password, 10);

    const created = await prisma.$transaction(async (tx) => {
      const user = await tx.user.create({
        data: {
          email,
          fullName,
          username,
          passwordHash: hashedPassword,
          isApproved: isApproved ?? false,
          activeBranchId: uniqueBranchIds[0] || null,
        },
      });

      if (uniqueRoleIds.length) {
        await tx.userRole.createMany({
          data: uniqueRoleIds.map((roleId) => ({ userId: user.id, roleId })),
          skipDuplicates: true,
        });
      }
      if (uniqueBranchIds.length) {
        await tx.userBranch.createMany({
          data: uniqueBranchIds.map((branchId) => ({ userId: user.id, branchId })),
          skipDuplicates: true,
        });
      }

      return tx.user.findUnique({ where: { id: user.id }, select: userSelect });
    });

    return res.status(201).json(formatUser(created));
  } catch (err) {
    if (err.status) return res.status(err.status).json({ error: err.message });
    if (err.code === "P2002") {
      const field = err.meta?.target?.[0] || "field";
      return res.status(409).json({ error: `A user with that ${field} already exists` });
    }
    return next(err);
  }
});

router.patch("/:id", requireAuth, requireUserManager, async (req, res, next) => {
  try {
    const { id } = req.params;
    await assertCanAccessUser(prisma, req, id);
    const existing = await prisma.user.findUnique({ where: { id } });
    if (!existing) return res.status(404).json({ error: "User not found" });

    const { email, fullName, username, password, isApproved, roleIds, branchIds } = req.body || {};

    const data = {};
    if (email !== undefined) data.email = email;
    if (fullName !== undefined) data.fullName = fullName;
    if (username !== undefined) data.username = username;
    if (isApproved !== undefined) data.isApproved = isApproved;
    if (password) data.passwordHash = await bcrypt.hash(password, 10);

    const uniqueRoleIds = roleIds !== undefined
      ? Array.from(new Set(roleIds.map((x) => String(x).trim()).filter(Boolean)))
      : null;
    const uniqueBranchIds = branchIds !== undefined
      ? Array.from(new Set(branchIds.map((x) => String(x).trim()).filter(Boolean)))
      : null;

    if (uniqueRoleIds !== null) {
      await assertCanAssignRoleIds(prisma, req, uniqueRoleIds);
    }
    if (uniqueBranchIds?.length) {
      const branches = await prisma.branch.findMany({ where: { id: { in: uniqueBranchIds } }, select: { id: true } });
      if (branches.length !== uniqueBranchIds.length) return res.status(400).json({ error: "One or more branches are invalid" });
    }

    const updated = await prisma.$transaction(async (tx) => {
      if (Object.keys(data).length) {
        await tx.user.update({ where: { id }, data });
      }

      if (uniqueRoleIds !== null) {
        await tx.userRole.deleteMany({ where: { userId: id } });
        if (uniqueRoleIds.length) {
          await tx.userRole.createMany({
            data: uniqueRoleIds.map((roleId) => ({ userId: id, roleId })),
            skipDuplicates: true,
          });
        }
      }

      if (uniqueBranchIds !== null) {
        await tx.userBranch.deleteMany({ where: { userId: id } });
        if (uniqueBranchIds.length) {
          await tx.userBranch.createMany({
            data: uniqueBranchIds.map((branchId) => ({ userId: id, branchId })),
            skipDuplicates: true,
          });
          await tx.user.update({ where: { id }, data: { activeBranchId: uniqueBranchIds[0] } });
        }
      }

      return tx.user.findUnique({ where: { id }, select: userSelect });
    });

    return res.json(formatUser(updated));
  } catch (err) {
    if (err.status) return res.status(err.status).json({ error: err.message });
    if (err.code === "P2002") {
      const field = err.meta?.target?.[0] || "field";
      return res.status(409).json({ error: `A user with that ${field} already exists` });
    }
    return next(err);
  }
});

router.delete("/:id", requireAuth, requireUserManager, async (req, res, next) => {
  try {
    const { id } = req.params;
    if (req.user.sub === id) {
      return res.status(403).json({ error: "You cannot delete your own account" });
    }

    await assertCanAccessUser(prisma, req, id);
    const existing = await prisma.user.findUnique({ where: { id } });
    if (!existing) return res.status(404).json({ error: "User not found" });

    await prisma.$transaction(async (tx) => {
      await tx.userRole.deleteMany({ where: { userId: id } });
      await tx.userBranch.deleteMany({ where: { userId: id } });
      await tx.user.delete({ where: { id } });
    });

    return res.json({ message: "User deleted successfully" });
  } catch (err) {
    if (err.status) return res.status(err.status).json({ error: err.message });
    return next(err);
  }
});

router.put("/:id/roles", requireAuth, requireUserManager, async (req, res, next) => {
  try {
    const { id } = req.params;
    await assertCanAccessUser(prisma, req, id);
    const user = await prisma.user.findUnique({ where: { id } });
    if (!user) return res.status(404).json({ error: "Not found" });

    const { roleIds } = req.body || {};
    if (!Array.isArray(roleIds)) return res.status(400).json({ error: "roleIds must be an array" });

    const unique = Array.from(new Set(roleIds.map((x) => String(x).trim()).filter(Boolean)));
    await assertCanAssignRoleIds(prisma, req, unique);

    const updated = await prisma.$transaction(async (tx) => {
      await tx.userRole.deleteMany({ where: { userId: id } });
      if (unique.length) {
        await tx.userRole.createMany({
          data: unique.map((roleId) => ({ userId: id, roleId })),
          skipDuplicates: true,
        });
      }
      return tx.user.findUnique({
        where: { id },
        select: {
          id: true,
          email: true,
          username: true,
          fullName: true,
          isApproved: true,
          roles: { select: { role: { select: { id: true, name: true } } } },
        },
      });
    });

    return res.json({
      id: updated.id,
      email: updated.email,
      username: updated.username,
      fullName: updated.fullName,
      isApproved: updated.isApproved,
      roles: updated.roles.map((r) => r.role),
      roleIds: updated.roles.map((r) => r.role.id),
    });
  } catch (err) {
    if (err.status) return res.status(err.status).json({ error: err.message });
    return next(err);
  }
});

module.exports = router;
