const { Router } = require("express");
const crypto = require("crypto");
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const prisma = require("../prisma");
const { requireAuth } = require("../middleware/auth");
const { sendVerificationEmail, sendPasswordResetEmail } = require("../lib/mailer");
const rateLimit = require("express-rate-limit");

const router = Router();

const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 50,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: "Too many attempts. Please try again later." },
});

const strictAuthLimiter = rateLimit({
  windowMs: 60 * 60 * 1000,
  max: 15,
  standardHeaders: true,
  legacyHeaders: false,
  message: { error: "Too many attempts. Please try again in an hour." },
});

function isEmail(str) {
  return typeof str === "string" && str.includes("@");
}

const userInclude = {
  roles: { include: { role: { include: { permissions: { include: { permission: true } } } } } },
  activeBranch: true,
  branches: { include: { branch: true } },
};

function signAccessToken(user) {
  const roles = user.roles?.map((r) => r.role.name) ?? [];
  const permissions =
    user.roles?.flatMap((r) => r.role.permissions.map((p) => p.permission.name)) ?? [];

  const payload = {
    sub: user.id,
    email: user.email,
    username: user.username,
    roles,
    permissions,
    approved: Boolean(user.isApproved),
    activeBranchId: user.activeBranchId || null,
    activeBranchName: user.activeBranch?.name || null,
  };
  return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: "7d" });
}

function buildSafeUser(user) {
  return {
    id: user.id,
    email: user.email,
    fullName: user.fullName,
    username: user.username,
    isVerified: user.isVerified,
    emailVerified: user.isVerified,
    isApproved: user.isApproved,
    roles: user.roles.map((r) => r.role.name),
    permissions: user.roles.flatMap((r) => r.role.permissions.map((p) => p.permission.name)),
    activeBranchId: user.activeBranchId || null,
    activeBranch: user.activeBranch || null,
    branches: (user.branches || []).map((ub) => ub.branch),
  };
}

function isAdminLike(user) {
  const roles = user?.roles?.map((r) => (r.role ? r.role.name : r)) ?? [];
  const perms =
    user?.roles?.flatMap((r) => (r.role?.permissions || []).map((p) => p.permission?.name)) ?? [];
  return roles.includes("Admin") || roles.includes("Super Admin") || perms.includes("admin:*");
}

async function buildSafeUserWithBranches(user) {
  if (!isAdminLike(user)) return buildSafeUser(user);
  const branches = await prisma.branch.findMany({
    orderBy: [{ sortOrder: "asc" }, { name: "asc" }],
  });
  const safe = buildSafeUser(user);
  safe.branches = branches;
  return safe;
}

router.post("/register", strictAuthLimiter, async (req, res, next) => {
  try {
    const { email, password, fullName, username } = req.body || {};
    if (!email || !password || !fullName) {
      return res.status(400).json({ error: "Missing required fields" });
    }

    const existingEmail = await prisma.user.findUnique({ where: { email } });
    if (existingEmail) return res.status(409).json({ error: "Email already registered" });

    if (username) {
      const existingUsername = await prisma.user.findUnique({ where: { username } });
      if (existingUsername) return res.status(409).json({ error: "Username already taken" });
    }

    const passwordHash = await bcrypt.hash(password, 10);
    const verificationToken = crypto.randomBytes(32).toString("hex");
    const verificationTokenExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000);

    const defaultRole = await prisma.role.findUnique({ where: { name: "User" } });
    if (!defaultRole) {
      return res.status(500).json({ error: "Default role not seeded" });
    }

    const user = await prisma.user.create({
      data: {
        email,
        fullName,
        username: username || null,
        passwordHash,
        isVerified: false,
        isApproved: false,
        verificationToken,
        verificationTokenExpiry,
        roles: { create: [{ roleId: defaultRole.id }] },
      },
      include: userInclude,
    });

    sendVerificationEmail(email, verificationToken).catch((err) =>
      console.error("[Mail] Verification email failed:", err.message)
    );

    const accessToken = signAccessToken(user);
    const response = { accessToken, user: await buildSafeUserWithBranches(user) };
    if (process.env.NODE_ENV !== "production") {
      response.verificationToken = verificationToken;
    }
    return res.json(response);
  } catch (err) {
    return next(err);
  }
});

router.post("/login", strictAuthLimiter, async (req, res, next) => {
  try {
    const { email, password } = req.body || {};
    if (!email || !password) {
      return res.status(400).json({ error: "Missing credentials" });
    }

    const where = isEmail(email) ? { email } : { username: email };
    const userWithHash = await prisma.user.findUnique({
      where,
      include: userInclude,
    });

    if (!userWithHash) return res.status(401).json({ error: "Invalid credentials" });

    const ok = await bcrypt.compare(password, userWithHash.passwordHash);
    if (!ok) return res.status(401).json({ error: "Invalid credentials" });

    const accessToken = signAccessToken(userWithHash);
    return res.json({ accessToken, user: await buildSafeUserWithBranches(userWithHash) });
  } catch (err) {
    return next(err);
  }
});

router.get("/me", requireAuth, async (req, res, next) => {
  try {
    const id = req.user?.sub;
    const user = await prisma.user.findUnique({
      where: { id },
      include: userInclude,
    });
    if (!user) return res.status(404).json({ error: "Not found" });

    return res.json(await buildSafeUserWithBranches(user));
  } catch (err) {
    return next(err);
  }
});

router.post("/switch-branch", requireAuth, async (req, res, next) => {
  try {
    const userId = req.user.sub;
    const { branchId } = req.body || {};
    if (!branchId) return res.status(400).json({ error: "Missing branchId" });

    const userRecord = await prisma.user.findUnique({ where: { id: userId }, include: userInclude });
    if (!userRecord) return res.status(404).json({ error: "Not found" });

    if (!isAdminLike(userRecord)) {
      const membership = await prisma.userBranch.findUnique({
        where: { userId_branchId: { userId, branchId } },
      });
      if (!membership) {
        return res.status(403).json({ error: "You are not assigned to this branch" });
      }
    }

    const user = await prisma.user.update({
      where: { id: userId },
      data: { activeBranchId: branchId },
      include: userInclude,
    });

    const accessToken = signAccessToken(user);
    return res.json({ accessToken, user: await buildSafeUserWithBranches(user) });
  } catch (err) {
    return next(err);
  }
});

router.post("/forgot-password", strictAuthLimiter, async (req, res, next) => {
  try {
    const { email } = req.body || {};
    if (!email) return res.status(400).json({ error: "Email is required" });

    const user = await prisma.user.findUnique({ where: { email } });

    if (user) {
      const resetToken = crypto.randomBytes(32).toString("hex");
      const passwordResetTokenExpiry = new Date(Date.now() + 60 * 60 * 1000);

      await prisma.user.update({
        where: { id: user.id },
        data: { passwordResetToken: resetToken, passwordResetTokenExpiry },
      });

      sendPasswordResetEmail(email, resetToken).catch((err) =>
        console.error("[Mail] Password reset email failed:", err.message)
      );
    }

    const response = { message: "If that email exists, a reset link has been sent." };
    if (process.env.NODE_ENV !== "production" && user) {
      const updated = await prisma.user.findUnique({ where: { email } });
      response.resetToken = updated.passwordResetToken;
    }
    return res.json(response);
  } catch (err) {
    return next(err);
  }
});

router.post("/reset-password", strictAuthLimiter, async (req, res, next) => {
  try {
    const { token, password } = req.body || {};
    if (!token || !password) {
      return res.status(400).json({ error: "Token and password are required" });
    }

    const user = await prisma.user.findFirst({
      where: {
        passwordResetToken: token,
        passwordResetTokenExpiry: { gt: new Date() },
      },
    });

    if (!user) {
      return res.status(400).json({ error: "Invalid or expired reset token" });
    }

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

    await prisma.user.update({
      where: { id: user.id },
      data: {
        passwordHash,
        passwordResetToken: null,
        passwordResetTokenExpiry: null,
      },
    });

    return res.json({ message: "Password reset successfully" });
  } catch (err) {
    return next(err);
  }
});

router.post("/verify-email", authLimiter, async (req, res, next) => {
  try {
    const { token } = req.body || {};
    if (!token) return res.status(400).json({ error: "Token is required" });

    const user = await prisma.user.findFirst({
      where: {
        verificationToken: token,
        verificationTokenExpiry: { gt: new Date() },
      },
    });

    if (!user) {
      return res.status(400).json({ error: "Invalid or expired verification token" });
    }

    await prisma.user.update({
      where: { id: user.id },
      data: {
        isVerified: true,
        verificationToken: null,
        verificationTokenExpiry: null,
      },
    });

    return res.json({ message: "Email verified successfully" });
  } catch (err) {
    return next(err);
  }
});

router.post("/resend-verification", authLimiter, async (req, res, next) => {
  try {
    const { email } = req.body || {};
    if (!email) return res.status(400).json({ error: "Email is required" });

    const user = await prisma.user.findUnique({ where: { email } });

    if (user && !user.isVerified) {
      const verificationToken = crypto.randomBytes(32).toString("hex");
      const verificationTokenExpiry = new Date(Date.now() + 24 * 60 * 60 * 1000);

      await prisma.user.update({
        where: { id: user.id },
        data: { verificationToken, verificationTokenExpiry },
      });

      sendVerificationEmail(email, verificationToken).catch((err) =>
        console.error("[Mail] Verification email failed:", err.message)
      );
    }

    const response = { message: "If that email exists, a verification email has been sent." };
    if (process.env.NODE_ENV !== "production" && user && !user.isVerified) {
      const updated = await prisma.user.findUnique({ where: { email } });
      response.verificationToken = updated.verificationToken;
    }
    return res.json(response);
  } catch (err) {
    return next(err);
  }
});

module.exports = router;

