import bcrypt from "bcryptjs";
import { Router } from "express";
import { z } from "zod";
import { isSystemAdmin } from "../lib/authz";
import { prisma } from "../lib/prisma";
import { uploadAvatar } from "../lib/upload";
import { HttpError } from "../middleware/errorHandler";
import { requireAuth } from "../middleware/requireAuth";

export const usersRouter = Router();

usersRouter.use(requireAuth);

function publicUser(user: {
  id: string;
  email: string;
  name: string;
  username: string;
  title: string | null;
  avatarUrl: string | null;
  avatarColor: string;
  emailVerified: boolean;
  isSystemAdmin: boolean;
}) {
  return {
    id: user.id,
    email: user.email,
    name: user.name,
    username: user.username,
    title: user.title,
    avatarUrl: user.avatarUrl,
    avatarColor: user.avatarColor,
    emailVerified: user.emailVerified,
    isSystemAdmin: user.isSystemAdmin,
  };
}

const updateProfileSchema = z.object({
  name: z.string().min(1).max(100).optional(),
  title: z.string().max(100).nullable().optional(),
});

usersRouter.patch("/me", async (req, res, next) => {
  try {
    const data = updateProfileSchema.parse(req.body);
    const user = await prisma.user.update({
      where: { id: req.userId },
      data,
    });
    res.json({ user: publicUser(user) });
  } catch (err) {
    next(err);
  }
});

usersRouter.post("/me/avatar", uploadAvatar.single("avatar"), async (req, res, next) => {
  try {
    if (!req.file) {
      throw new HttpError(400, "No file uploaded");
    }
    const avatarUrl = `/uploads/avatars/${req.file.filename}`;
    const user = await prisma.user.update({
      where: { id: req.userId },
      data: { avatarUrl },
    });
    res.json({ user: publicUser(user) });
  } catch (err) {
    next(err);
  }
});

const changePasswordSchema = z.object({
  currentPassword: z.string().min(1),
  newPassword: z.string().min(8).max(200),
});

usersRouter.post("/me/change-password", async (req, res, next) => {
  try {
    const data = changePasswordSchema.parse(req.body);
    const user = await prisma.user.findUnique({
      where: { id: req.userId },
      omit: { passwordHash: false },
    });
    if (!user) throw new HttpError(401, "Not authenticated");

    const valid = await bcrypt.compare(data.currentPassword, user.passwordHash);
    if (!valid) throw new HttpError(400, "Current password is incorrect");

    const passwordHash = await bcrypt.hash(data.newPassword, 12);
    await prisma.user.update({ where: { id: req.userId }, data: { passwordHash } });

    res.status(200).json({ message: "Password updated." });
  } catch (err) {
    next(err);
  }
});

const notificationPreferencesSchema = z.object({
  emailOnMention: z.boolean().optional(),
  emailOnAssignment: z.boolean().optional(),
  emailOnDueDate: z.boolean().optional(),
  emailOnComment: z.boolean().optional(),
  emailOnBoardInvite: z.boolean().optional(),
  emailOnDriveShare: z.boolean().optional(),
});

usersRouter.get("/me/notification-preferences", async (req, res, next) => {
  try {
    const prefs = await prisma.notificationPreference.upsert({
      where: { userId: req.userId! },
      update: {},
      create: { userId: req.userId! },
    });
    res.json({ preferences: prefs });
  } catch (err) {
    next(err);
  }
});

usersRouter.patch("/me/notification-preferences", async (req, res, next) => {
  try {
    const data = notificationPreferencesSchema.parse(req.body);
    const prefs = await prisma.notificationPreference.upsert({
      where: { userId: req.userId! },
      update: data,
      create: { userId: req.userId!, ...data },
    });
    res.json({ preferences: prefs });
  } catch (err) {
    next(err);
  }
});

// ---------- System admin ----------

const systemAdminSchema = z.object({ isSystemAdmin: z.boolean() });

usersRouter.put("/:id/system-admin", async (req, res, next) => {
  try {
    if (!(await isSystemAdmin(req.userId!))) {
      throw new HttpError(403, "Only system admins can grant or revoke system admin access");
    }
    if (req.params.id === req.userId) {
      throw new HttpError(400, "You can't change your own system admin status");
    }

    const data = systemAdminSchema.parse(req.body);
    const user = await prisma.user.update({
      where: { id: req.params.id },
      data: { isSystemAdmin: data.isSystemAdmin },
    });
    res.json({ user: publicUser(user) });
  } catch (err) {
    next(err);
  }
});
