import { Router, Request, Response } from 'express';
import { prisma } from '../db/prisma';
import { requireAuth, requires } from '../middleware/auth';
import {
  PERMISSIONS,
  ASSIGNABLE_ROLES,
  isAssignableRole,
  isValidPermissionKey,
} from '../constants/permissions';

const router = Router();

// Returns the full permission registry plus the assignable roles. Adding a key
// to the registry surfaces it here automatically.
router.get(
  '/permissions',
  requireAuth,
  requires('roles.edit_permissions'),
  async (_req: Request, res: Response) => {
    res.json({
      permissions: PERMISSIONS,
      roles: ASSIGNABLE_ROLES,
    });
  }
);

// Returns each assignable role with its current permission set.
router.get(
  '/roles',
  requireAuth,
  requires('roles.edit_permissions'),
  async (_req: Request, res: Response) => {
    try {
      const rows = await prisma.rolePermission.findMany();
      const byRole: Record<string, string[]> = {};
      for (const role of ASSIGNABLE_ROLES) byRole[role] = [];
      for (const row of rows) {
        if (byRole[row.role]) byRole[row.role].push(row.permission);
      }
      res.json({
        roles: ASSIGNABLE_ROLES.map((role) => ({ role, permissions: byRole[role] })),
      });
    } catch (err) {
      console.error('List roles error:', err);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

// Replaces a role's permission set. Validates the role and every key, prevents
// privilege escalation, and prevents the caller from locking themselves out.
router.put(
  '/roles/:role/permissions',
  requireAuth,
  requires('roles.edit_permissions'),
  async (req: Request, res: Response) => {
    try {
      const roleRaw = req.params.role;
      const role = typeof roleRaw === 'string' ? roleRaw : Array.isArray(roleRaw) ? roleRaw[0] : '';
      if (!isAssignableRole(role)) {
        res.status(400).json({ error: 'Invalid role' });
        return;
      }

      const { permissions } = req.body as { permissions?: unknown };
      if (!Array.isArray(permissions) || permissions.some((p) => typeof p !== 'string')) {
        res.status(400).json({ error: 'permissions must be an array of strings' });
        return;
      }

      const requested = [...new Set(permissions as string[])];
      const unknown = requested.filter((p) => !isValidPermissionKey(p));
      if (unknown.length > 0) {
        res.status(400).json({ error: `Unknown permission keys: ${unknown.join(', ')}` });
        return;
      }

      const caller = req.access!;
      const newSet = new Set(requested);

      const existingRows = await prisma.rolePermission.findMany({ where: { role } });
      const currentSet = new Set(existingRows.map((r) => r.permission));

      // No privilege escalation: any newly granted permission must be held by the
      // caller. SuperAdmin holds everything and is exempt.
      if (!caller.isSuperAdmin) {
        const added = requested.filter((p) => !currentSet.has(p));
        const escalated = added.filter((p) => !caller.permissions.has(p));
        if (escalated.length > 0) {
          res.status(403).json({
            error: `You cannot grant permissions you do not hold: ${escalated.join(', ')}`,
          });
          return;
        }

        // Prevent self-lockout: when editing your own role, you cannot drop the
        // permissions that keep you able to manage roles and users.
        if (caller.role === role) {
          const protectedKeys = ['users.assign_role', 'roles.edit_permissions'];
          const lockedOut = protectedKeys.filter(
            (k) => caller.permissions.has(k) && !newSet.has(k)
          );
          if (lockedOut.length > 0) {
            res.status(403).json({
              error: `You cannot remove your own ${lockedOut.join(', ')}`,
            });
            return;
          }
        }
      }

      // Replace the role's permission rows transactionally.
      await prisma.$transaction([
        prisma.rolePermission.deleteMany({ where: { role } }),
        prisma.rolePermission.createMany({
          data: requested.map((permission) => ({ role, permission })),
        }),
      ]);

      res.json({ role, permissions: requested });
    } catch (err) {
      console.error('Update role permissions error:', err);
      res.status(500).json({ error: 'Internal server error' });
    }
  }
);

export default router;
