/**
 * Organization Admin — User Management Routes
 *
 * Org-level admin routes for managing users within the admin's own company.
 * All routes require authenticate + requireOrgAdmin middleware.
 * Queries are scoped to the org admin's company via req.orgContext.
 */

import { Router, Request, Response } from 'express';
import { body, param, query, validationResult } from 'express-validator';
import bcrypt from 'bcryptjs';
import { getModels } from '../models';
import { authenticate, generateToken } from '../middleware/auth';
import { requireOrgAdmin, orgUserFilter, orgScopeFilter } from '../middleware/orgAdmin';
import { asyncHandler } from '../middleware/errorHandler';
import { invalidatePermissionCache } from '../middleware/permissions';
import { notificationService } from '../services/notificationService';
import { sendAccountCreatedEmail } from '../services/email';
import { logAudit } from '../utils/auditLogger';

export const orgAdminUsersRoutes = Router();

// Apply authentication and org-admin requirement to all routes
orgAdminUsersRoutes.use(authenticate);
orgAdminUsersRoutes.use(requireOrgAdmin);

// ============================================
// GET / — List users in org, with search/filter/pagination
// ============================================

orgAdminUsersRoutes.get(
  '/',
  [
    query('page').optional().isInt({ min: 1 }),
    query('limit').optional().isInt({ min: 1, max: 500 }),
    query('search').optional().trim(),
    query('role').optional().isIn(['admin', 'editor', 'viewer']),
    query('status').optional().isIn(['active', 'inactive', 'suspended']),
  ],
  asyncHandler(async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { User } = getModels();
    const orgContext = req.orgContext!;

    const page = parseInt(req.query.page as string) || 1;
    const limit = parseInt(req.query.limit as string) || 20;
    const search = (req.query.search as string) || '';
    const role = req.query.role as string | undefined;
    const status = req.query.status as string | undefined;

    // Build base filter scoped to org
    const baseFilter = orgUserFilter(orgContext);

    const searchFilter: any = { ...baseFilter };
    if (search) {
      searchFilter.$or = [
        { name: { $regex: search, $options: 'i' } },
        { email: { $regex: search, $options: 'i' } },
        { username: { $regex: search, $options: 'i' } },
      ];
      // If baseFilter already has keys, merge with $and
      if (baseFilter.$or || Object.keys(baseFilter).length > 0) {
        // baseFilter already applied via spread; $or is additive
      }
    }
    if (role) searchFilter.role = role;
    if (status) searchFilter.status = status;

    // If baseFilter has compound keys, restructure to use $and
    // to avoid conflicting with search $or
    let filter: any;
    if (search && Object.keys(baseFilter).length > 0) {
      // Merge: baseFilter conditions AND search conditions
      const searchConditions: any[] = [
        { name: { $regex: search, $options: 'i' } },
        { email: { $regex: search, $options: 'i' } },
        { username: { $regex: search, $options: 'i' } },
      ];
      filter = {
        ...baseFilter,
        $or: searchConditions,
      };
      if (role) filter.role = role;
      if (status) filter.status = status;
    } else {
      filter = searchFilter;
    }

    const skip = (page - 1) * limit;
    const [users, total] = await Promise.all([
      User.find(filter).select('-passwordHash').sort({ createdAt: -1 }).skip(skip).limit(limit).lean(),
      User.countDocuments(filter),
    ]);

    res.json({ users, total, page, totalPages: Math.ceil(total / limit) });
  })
);

// ============================================
// GET /:id — Get user detail (must be in same company)
// ============================================

orgAdminUsersRoutes.get(
  '/:id',
  [param('id').notEmpty()],
  asyncHandler(async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { User } = getModels();
    const orgContext = req.orgContext!;
    const { id } = req.params;

    const user = await User.findById(id).select('-passwordHash').lean();
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Super-admins are invisible to org admins
    if (user.role === 'super-admin' && !orgContext.isSuperAdmin) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Must belong to at least one of the admin's managed companies (org admin scope)
    if (!orgContext.isSuperAdmin) {
      const companyIds = (user as any).companyIds || [];
      const managedSet = orgContext.managedCompanyIds?.length ? orgContext.managedCompanyIds : [orgContext.companyId];
      const intersects = companyIds.some((cid: string) => managedSet.includes(cid));
      if (!intersects) {
        res.status(404).json({ error: 'User not found' });
        return;
      }
    }

    res.json(user);
  })
);

// ============================================
// POST / — Create user, add to org's company
// ============================================

orgAdminUsersRoutes.post(
  '/',
  [
    body('email').isEmail().withMessage('Valid email is required').normalizeEmail(),
    body('name').trim().notEmpty().withMessage('Name is required')
      .isLength({ max: 100 }).withMessage('Name cannot exceed 100 characters'),
    body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters'),
    body('role').optional().isIn(['admin', 'editor', 'viewer']),
    body('username').optional().trim().isLength({ min: 3, max: 30 }),
    body('phone').optional().trim(),
    body('roleId').optional().trim(),
    body('companyIds').optional().isArray().withMessage('companyIds must be an array of company IDs'),
  ],
  asyncHandler(async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { User, Role, Company } = getModels();
    const orgContext = req.orgContext!;
    const { email, name, password, role, username, phone, roleId } = req.body;

    // Resolve the companies the new user should belong to. Defaults to ALL of
    // the admin's managed companies (so the admin creates the user once and they
    // apply across every linked company). The admin may narrow this via the
    // company multi-select on the user form.
    const managed = orgContext.isSuperAdmin
      ? []
      : (orgContext.managedCompanyIds?.length ? orgContext.managedCompanyIds : [orgContext.companyId]);
    let requestedCompanyIds: string[] = Array.isArray(req.body.companyIds) ? req.body.companyIds : [];
    if (requestedCompanyIds.length === 0) {
      requestedCompanyIds = managed.length > 0 ? [...managed] : [orgContext.companyId];
    }
    // Org admins can only grant access to companies they manage.
    if (!orgContext.isSuperAdmin && managed.length > 0) {
      const invalid = requestedCompanyIds.filter((cid: string) => !managed.includes(cid));
      if (invalid.length > 0) {
        res.status(403).json({ error: 'You can only grant access to companies you manage' });
        return;
      }
    }
    if (requestedCompanyIds.length === 0) {
      res.status(400).json({ error: 'Select at least one company for the user' });
      return;
    }

    // Cannot assign super-admin role
    if (role === 'super-admin') {
      res.status(403).json({ error: 'Cannot assign Super Admin role' });
      return;
    }
    // The Admin role is reserved (org admins are granted via the Super Admin
    // Organizations page) and must not be assignable from the user form.
    if (role === 'admin') {
      res.status(403).json({ error: 'The Admin role cannot be assigned' });
      return;
    }

    // If roleId provided, must be visible to this org (global or org-scoped)
    if (roleId) {
      const roleFilter: any = orgScopeFilter(orgContext);
      const visibleRole = await Role.findOne({ _id: roleId, ...roleFilter }).lean();
      if (!visibleRole) {
        res.status(403).json({ error: 'Cannot assign a role that is not visible to your organisation' });
        return;
      }
      if (visibleRole.name === 'admin') {
        res.status(403).json({ error: 'The Admin role cannot be assigned' });
        return;
      }
    }

    // Check email uniqueness
    const existingUser = await User.findOne({ email: email.toLowerCase() });
    if (existingUser) {
      res.status(409).json({ error: 'A user with this email already exists' });
      return;
    }

    // Check username uniqueness if provided
    if (username) {
      const existingUsername = await User.findOne({ username });
      if (existingUsername) {
        res.status(409).json({ error: 'A user with this username already exists' });
        return;
      }
    }

    // Hash password
    const salt = await bcrypt.genSalt(12);
    const passwordHash = await bcrypt.hash(password, salt);

    // Add user to the selected companies (defaults to all of the admin's managed
    // companies). The user's active company is the first selected company.
    const companyId = orgContext.companyId;
    const targetCompanyIds = requestedCompanyIds;

    const user = await User.create({
      email: email.toLowerCase(),
      name: name.trim(),
      passwordHash,
      role: role || 'viewer',
      username: username || undefined,
      phone: phone || undefined,
      status: 'active',
      companyIds: targetCompanyIds,
      activeCompanyId: targetCompanyIds.includes(companyId) ? companyId : targetCompanyIds[0],
      mustChangePassword: true,
      ...(roleId ? { roleId } : {}),
    });

    // Keep the denormalized Company.userIds lists in sync for every company
    // the user was added to.
    try {
      await Company.updateMany(
        { _id: { $in: targetCompanyIds } },
        { $addToSet: { userIds: (user as any)._id.toString() } }
      );
    } catch { /* non-blocking — User.companyIds is the authoritative side */ }

    // Return user without password hash
    const userObj = (user as any).toObject ? (user as any).toObject() : user;
    const { passwordHash: _, ...userWithoutPassword } = userObj;

    await logAudit({
      userId: req.user!._id.toString(),
      userEmail: req.user!.email,
      action: 'org-user.create',
      resource: 'User',
      resourceId: (user as any)._id?.toString(),
      companyId: orgContext.companyId,
      details: { email: userObj.email, name: userObj.name, role: userObj.role, companyIds: targetCompanyIds },
      req,
    });

    // Welcome the new member and let the org's other admins know. Both are
    // fire-and-forget — neither can fail the account creation.
    void notificationService.notifyUser((user as any)._id.toString(), {
      type: 'user.joined',
      title: 'Welcome to the workspace',
      message: 'Your account has been created. You will be asked to set a new password on first sign-in.',
      organizationId: orgContext.companyId,
      actorUserId: req.user!._id.toString(),
      actorName: req.user!.name,
      notifyActor: true,
    });

    void notificationService.notifyOrgRole(orgContext.companyId, 'admin', {
      type: 'user.joined',
      message: `${userObj.name} (${userObj.email}) was added as ${userObj.role}.`,
      entityType: 'user',
      entityId: (user as any)._id?.toString(),
      actionUrl: '/org-admin/users',
      actorUserId: req.user!._id.toString(),
      actorName: req.user!.name,
    });

    // The in-app welcome above only reaches someone who is already signed in,
    // which a brand-new member never is — the existing 'account-created'
    // template is how they learn the account exists. Detached and swallowed:
    // sendAccountCreatedEmail never throws, and mail must not be able to fail
    // an account that has already been created and audited.
    void (async () => {
      try {
        const company = await Company.findById(orgContext.companyId).lean();
        const result = await sendAccountCreatedEmail({
          to: userObj.email,
          userName: userObj.name,
          companyName: (company as any)?.name || '',
          accountType: userObj.role,
          createdBy: req.user!.name,
        });
        if (!result.success && !result.skipped) {
          console.error(`[OrgAdminUsers] Account-created email to ${userObj.email} failed: ${result.error}`);
        }
      } catch (err: unknown) {
        const detail = err instanceof Error ? err.message : String(err);
        console.error(`[OrgAdminUsers] Account-created email to ${userObj.email} failed: ${detail}`);
      }
    })();

    res.status(201).json(userWithoutPassword);
  })
);

// ============================================
// PUT /:id — Update user (cannot modify super-admin, validate roleId scope)
// ============================================

orgAdminUsersRoutes.put(
  '/:id',
  [
    param('id').notEmpty(),
    body('name').optional().trim().isLength({ min: 1, max: 100 }),
    body('email').optional().isEmail().normalizeEmail(),
    body('username').optional().trim().isLength({ min: 3, max: 30 }),
    body('phone').optional().trim(),
    body('status').optional().isIn(['active', 'inactive', 'suspended']),
    body('roleId').optional().trim(),
    body('notes').optional().trim().isLength({ max: 1000 }),
    body('profileImage').optional().trim(),
    body('companyIds').optional().isArray().withMessage('companyIds must be an array of company IDs'),
  ],
  asyncHandler(async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { User, Role, Company } = getModels();
    const orgContext = req.orgContext!;
    const { id } = req.params;
    const { name, email, username, phone, status, roleId, notes, profileImage } = req.body;

    const user = await User.findById(id);
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Cannot modify super-admin users
    if (user.role === 'super-admin') {
      res.status(403).json({ error: 'Cannot modify Super Admin' });
      return;
    }

    // Must belong to at least one of the admin's managed companies (org admin scope)
    if (!orgContext.isSuperAdmin) {
      const companyIds = (user as any).companyIds || [];
      const managedSet = orgContext.managedCompanyIds?.length ? orgContext.managedCompanyIds : [orgContext.companyId];
      const intersects = companyIds.some((cid: string) => managedSet.includes(cid));
      if (!intersects) {
        res.status(404).json({ error: 'User not found' });
        return;
      }
    }

    // Validate roleId scope if provided
    if (roleId !== undefined && roleId !== null && roleId !== '') {
      const roleFilter: any = orgScopeFilter(orgContext);
      const visibleRole = await Role.findOne({ _id: roleId, ...roleFilter }).lean();
      if (!visibleRole) {
        res.status(403).json({ error: 'Cannot assign a role that is not visible to your organisation' });
        return;
      }
      if (visibleRole.name === 'admin') {
        res.status(403).json({ error: 'The Admin role cannot be assigned' });
        return;
      }
    }

    // Check email uniqueness if changing
    if (email && email.toLowerCase() !== user.email) {
      const existingUser = await User.findOne({ email: email.toLowerCase(), _id: { $ne: id } });
      if (existingUser) {
        res.status(409).json({ error: 'A user with this email already exists' });
        return;
      }
      user.email = email.toLowerCase();
    }

    // Check username uniqueness if changing
    if (username && username !== (user as any).username) {
      const existingUsername = await User.findOne({ username, _id: { $ne: id } });
      if (existingUsername) {
        res.status(409).json({ error: 'A user with this username already exists' });
        return;
      }
      (user as any).username = username;
    }

    if (name) user.name = name.trim();
    if (phone !== undefined) (user as any).phone = phone;
    if (status) user.status = status;
    if (roleId !== undefined) (user as any).roleId = roleId || undefined;
    if (notes !== undefined) (user as any).notes = notes;
    if (profileImage !== undefined) (user as any).profileImage = profileImage;

    // Update company memberships when provided. The admin may only toggle the
    // companies THEY manage; any existing memberships outside the managed set
    // (e.g. another org's company) are preserved so the admin cannot accidentally
    // strip access they don't own.
    if (Array.isArray(req.body.companyIds)) {
      const managedSet = orgContext.isSuperAdmin
        ? []
        : (orgContext.managedCompanyIds?.length ? orgContext.managedCompanyIds : [orgContext.companyId]);
      let selectedManaged: string[] = req.body.companyIds.filter((cid: string) => typeof cid === 'string');
      if (!orgContext.isSuperAdmin && managedSet.length > 0) {
        const invalid = selectedManaged.filter((cid: string) => !managedSet.includes(cid));
        if (invalid.length > 0) {
          res.status(403).json({ error: 'You can only grant access to companies you manage' });
          return;
        }
      }
      if (orgContext.isSuperAdmin && selectedManaged.length === 0) {
        res.status(400).json({ error: 'Select at least one company for the user' });
        return;
      }
      const existingCompanyIds: string[] = (user as any).companyIds || [];
      // Preserve outside-managed memberships (only super-admin has empty managedSet,
      // in which case nothing is "outside" and the full selection is applied).
      const outsideManaged = orgContext.isSuperAdmin
        ? []
        : existingCompanyIds.filter((cid: string) => !managedSet.includes(cid));
      const newCompanyIds = [...new Set([...selectedManaged, ...outsideManaged])];
      if (newCompanyIds.length === 0) {
        res.status(400).json({ error: 'Select at least one company for the user' });
        return;
      }

      const added = newCompanyIds.filter((cid: string) => !existingCompanyIds.includes(cid));
      const removed = existingCompanyIds.filter((cid: string) => !newCompanyIds.includes(cid));

      (user as any).companyIds = newCompanyIds;
      // If the user's active company was removed, fall back to the first remaining one.
      if (!(user as any).activeCompanyId || !newCompanyIds.includes((user as any).activeCompanyId)) {
        (user as any).activeCompanyId = newCompanyIds[0];
      }

      // Keep Company.userIds denormalized lists in sync for added/removed companies.
      try {
        if (added.length > 0) {
          await Company.updateMany(
            { _id: { $in: added } },
            { $addToSet: { userIds: id } }
          );
        }
        if (removed.length > 0) {
          await Company.updateMany(
            { _id: { $in: removed } },
            { $pull: { userIds: id } }
          );
        }
      } catch { /* non-blocking — User.companyIds is the authoritative side */ }
    }

    await user.save();

    // Audit log
    await logAudit({
      userId: req.user!._id.toString(),
      userEmail: req.user!.email,
      action: 'org-user.update',
      resource: 'User',
      resourceId: id,
      companyId: orgContext.companyId,
      details: { name: user.name, email: user.email, changedFields: Object.keys(req.body).filter(k => k !== 'password') },
      req,
    });

    // Invalidate permission cache if role, status, or company memberships changed.
    // Company membership changes can affect access across multiple companies, so
    // clear all of the user's cached permission resolutions.
    if (req.body.roleId || req.body.status || Array.isArray(req.body.companyIds)) {
      await invalidatePermissionCache(id);

      // A change to what someone can reach is worth telling them about — their
      // menus and permissions will look different on their next page load.
      if (req.body.roleId && String(id) !== req.user!._id.toString()) {
        void notificationService.notifyUser(String(id), {
          type: 'user.role_changed',
          message: 'Your access level was updated by an administrator.',
          organizationId: orgContext.companyId,
          actionUrl: '/dashboard',
          actorUserId: req.user!._id.toString(),
          actorName: req.user!.name,
        });
      }
    }

    // Return without password hash
    const userObj = (user as any).toObject ? (user as any).toObject() : { ...user };
    const { passwordHash: _, ...userWithoutPassword } = userObj;
    res.json(userWithoutPassword);
  })
);

// ============================================
// PATCH /:id/status — Activate / deactivate / suspend
// ============================================

orgAdminUsersRoutes.patch(
  '/:id/status',
  [
    param('id').notEmpty(),
    body('status').isIn(['active', 'inactive', 'suspended']).withMessage('Status must be active, inactive, or suspended'),
  ],
  asyncHandler(async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { User } = getModels();
    const orgContext = req.orgContext!;
    const { id } = req.params;
    const { status } = req.body;

    const user = await User.findById(id);
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Cannot modify super-admin
    if (user.role === 'super-admin') {
      res.status(403).json({ error: 'Cannot modify Super Admin status' });
      return;
    }

    // Must belong to at least one of the admin's managed companies (org admin scope)
    if (!orgContext.isSuperAdmin) {
      const companyIds = (user as any).companyIds || [];
      const managedSet = orgContext.managedCompanyIds?.length ? orgContext.managedCompanyIds : [orgContext.companyId];
      const intersects = companyIds.some((cid: string) => managedSet.includes(cid));
      if (!intersects) {
        res.status(404).json({ error: 'User not found' });
        return;
      }
    }

    user.status = status;
    await user.save();

    // Audit log
    await logAudit({
      userId: req.user!._id.toString(),
      userEmail: req.user!.email,
      action: 'org-user.status-change',
      resource: 'User',
      resourceId: id,
      companyId: orgContext.companyId,
      details: { name: user.name, email: user.email, newStatus: status },
      req,
    });

    // Invalidate permission cache — status change may affect access
    await invalidatePermissionCache(id, orgContext.companyId);

    // Losing access is the one status change worth raising: `user.deactivated`
    // is registered for exactly this and had no emitter, so suspending a member
    // was recorded in the audit trail and nowhere else. Sent to the org's other
    // admins (the service drops the one who did it) rather than to the account
    // itself, which can no longer sign in to read it. Re-activation has no
    // registered type and raises nothing.
    if (status !== 'active') {
      void notificationService.notifyOrgRole(orgContext.companyId, 'admin', {
        type: 'user.deactivated',
        title: status === 'suspended' ? 'Account suspended' : 'Account deactivated',
        message: `${user.name} (${user.email}) was set to ${status}.`,
        entityType: 'user',
        entityId: String(id),
        actionUrl: '/org-admin/users',
        actorUserId: req.user!._id.toString(),
        actorName: req.user!.name,
      });
    }

    res.json({
      id: (user as any)._id || (user as any).id,
      name: user.name,
      email: user.email,
      status: user.status,
    });
  })
);

// ============================================
// DELETE /:id — Cannot delete self or super-admin
// ============================================

orgAdminUsersRoutes.delete(
  '/:id',
  [param('id').notEmpty()],
  asyncHandler(async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { User } = getModels();
    const orgContext = req.orgContext!;
    const { id } = req.params;

    // Cannot delete yourself
    if (id === req.user!._id!.toString()) {
      res.status(400).json({ error: 'Cannot delete yourself' });
      return;
    }

    const user = await User.findById(id);
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Cannot delete super-admin
    if (user.role === 'super-admin') {
      res.status(403).json({ error: 'Cannot delete Super Admin' });
      return;
    }

    // Must belong to at least one of the admin's managed companies (org admin scope)
    if (!orgContext.isSuperAdmin) {
      const companyIds = (user as any).companyIds || [];
      const managedSet = orgContext.managedCompanyIds?.length ? orgContext.managedCompanyIds : [orgContext.companyId];
      const intersects = companyIds.some((cid: string) => managedSet.includes(cid));
      if (!intersects) {
        res.status(404).json({ error: 'User not found' });
        return;
      }
    }

    await User.findByIdAndDelete(id);

    await logAudit({
      userId: req.user!._id.toString(),
      userEmail: req.user!.email,
      action: 'org-user.delete',
      resource: 'User',
      resourceId: id,
      companyId: orgContext.companyId,
      details: { email: (user as any).email, name: (user as any).name },
      req,
    });

    res.json({ message: 'User deleted successfully' });
  })
);

// ============================================
// PATCH /:id/reset-password — Force password reset
// ============================================

orgAdminUsersRoutes.patch(
  '/:id/reset-password',
  [
    param('id').notEmpty(),
    body('newPassword').isLength({ min: 6 }).withMessage('New password must be at least 6 characters'),
  ],
  asyncHandler(async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { User } = getModels();
    const orgContext = req.orgContext!;
    const { id } = req.params;
    const { newPassword } = req.body;

    const user = await User.findById(id);
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Cannot modify super-admin
    if (user.role === 'super-admin') {
      res.status(403).json({ error: 'Cannot reset password for Super Admin' });
      return;
    }

    // Must belong to at least one of the admin's managed companies (org admin scope)
    if (!orgContext.isSuperAdmin) {
      const companyIds = (user as any).companyIds || [];
      const managedSet = orgContext.managedCompanyIds?.length ? orgContext.managedCompanyIds : [orgContext.companyId];
      const intersects = companyIds.some((cid: string) => managedSet.includes(cid));
      if (!intersects) {
        res.status(404).json({ error: 'User not found' });
        return;
      }
    }

    // Hash the new password
    const salt = await bcrypt.genSalt(12);
    user.passwordHash = await bcrypt.hash(newPassword, salt);
    (user as any).mustChangePassword = true;

    await user.save();

    // Audit log
    await logAudit({
      userId: req.user!._id.toString(),
      userEmail: req.user!.email,
      action: 'org-user.reset-password',
      resource: 'User',
      resourceId: id,
      companyId: orgContext.companyId,
      details: { targetEmail: user.email, targetName: user.name },
      req,
    });

    res.json({ message: 'Password reset successfully. User must change password on next login.' });
  })
);

// ============================================
// PATCH /:id/lock — Lock / unlock user
// ============================================

orgAdminUsersRoutes.patch(
  '/:id/lock',
  [
    param('id').notEmpty(),
    body('isLocked').isBoolean().withMessage('isLocked must be a boolean'),
  ],
  asyncHandler(async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { User } = getModels();
    const orgContext = req.orgContext!;
    const { id } = req.params;
    const { isLocked } = req.body;

    const user = await User.findById(id);
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Cannot modify super-admin
    if (user.role === 'super-admin') {
      res.status(403).json({ error: 'Cannot lock or unlock Super Admin' });
      return;
    }

    // Must belong to at least one of the admin's managed companies (org admin scope)
    if (!orgContext.isSuperAdmin) {
      const companyIds = (user as any).companyIds || [];
      const managedSet = orgContext.managedCompanyIds?.length ? orgContext.managedCompanyIds : [orgContext.companyId];
      const intersects = companyIds.some((cid: string) => managedSet.includes(cid));
      if (!intersects) {
        res.status(404).json({ error: 'User not found' });
        return;
      }
    }

    (user as any).isLocked = isLocked;
    await user.save();

    // Audit log
    await logAudit({
      userId: req.user!._id.toString(),
      userEmail: req.user!.email,
      action: isLocked ? 'org-user.lock' : 'org-user.unlock',
      resource: 'User',
      resourceId: id,
      companyId: orgContext.companyId,
      details: { name: user.name, email: user.email, isLocked },
      req,
    });

    res.json({
      id: (user as any)._id || (user as any).id,
      name: user.name,
      email: user.email,
      isLocked: (user as any).isLocked,
    });
  })
);

// ============================================
// GET /:id/permissions — Resolve effective permissions for a user in org context
// ============================================

orgAdminUsersRoutes.get(
  '/:id/permissions',
  [param('id').notEmpty()],
  asyncHandler(async (req: Request, res: Response) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      res.status(400).json({ error: 'Validation failed', details: errors.array() });
      return;
    }

    const { User, Role, UserAccessOverride } = getModels();
    const orgContext = req.orgContext!;
    const { id } = req.params;

    const user = await User.findById(id).lean();
    if (!user) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Cannot view super-admin permissions (unless you are super-admin yourself)
    if (user.role === 'super-admin' && !orgContext.isSuperAdmin) {
      res.status(404).json({ error: 'User not found' });
      return;
    }

    // Must belong to at least one of the admin's managed companies (org admin scope)
    if (!orgContext.isSuperAdmin) {
      const companyIds = (user as any).companyIds || [];
      const managedSet = orgContext.managedCompanyIds?.length ? orgContext.managedCompanyIds : [orgContext.companyId];
      const intersects = companyIds.some((cid: string) => managedSet.includes(cid));
      if (!intersects) {
        res.status(404).json({ error: 'User not found' });
        return;
      }
    }

    // Super-admin has all permissions
    if (user.role === 'super-admin') {
      const allModules = [
        'business-profile', 'founders', 'employees', 'products', 'product-categories',
        'icp-personas', 'competitors', 'brand', 'brand-strategy', 'visual-identity',
        'brand-manual', 'brand-assets', 'stationery', 'hr-assets', 'seo',
        'blog-content-os', 'case-studies', 'testimonials', 'faq-bank',
        'website-planner', 'newsletter-content-os', 'social-media-os',
        'landing-pages', 'whatsapp-nurturing', 'sales-scripts', 'sales-collateral',
        'video-content', 'books', 'ads', 'pr', 'email-templates', 'intro-scripts',
        'courses', 'events', 'guerrilla-marketing', 'interview-media-prep',
        'speaking-engagements', 'marketing-calendar', 'marketing-channels',
        'influencer', 'gmb', 'loyalty-programme', 'membership-plans',
        'referral-programme', 'sops', 'legal-documents', 'ai-processing',
        'magazine-sponsorship', 'funding', 'ai-chat',
      ];
      const allActions = ['view', 'create', 'edit', 'delete', 'ai-generate', 'export', 'manage'];
      const superAdminPermissions = allModules.map(module => ({
        module,
        actions: [...allActions],
      }));
      res.json({
        userId: (user as any)._id || (user as any).id,
        role: user.role,
        roleId: (user as any).roleId || null,
        rolePermissions: superAdminPermissions,
        overrides: null,
        resolvedPermissions: superAdminPermissions,
        isSuperAdmin: true,
      });
      return;
    }

    // Resolve permissions in the org's company context
    const companyId = orgContext.companyId;

    // Get role — must be visible to this org
    let role = null;
    if ((user as any).roleId) {
      const roleFilter: any = orgScopeFilter(orgContext);
      role = await Role.findOne({ _id: (user as any).roleId, ...roleFilter }).lean();
    }
    if (!role) {
      // Fallback: find a default/global role matching user.role
      const fallbackFilter: any = orgScopeFilter(orgContext);
      role = await Role.findOne({ name: user.role, isDefault: true, ...fallbackFilter }).lean();
    }

    const rolePermissions = role ? (role as any).permissions || [] : [];

    // Get user-specific overrides scoped to this company
    const override = await UserAccessOverride.findOne({ userId: id, companyId }).lean();

    const grants = override ? (override as any).grants || [] : [];
    const denies = override ? (override as any).denies || [] : [];

    // Resolve effective permissions: start with role, apply grants, then denies
    const permissionMap = new Map<string, Set<string>>();

    // Start with role permissions
    for (const perm of rolePermissions) {
      const key = perm.module + (perm.page ? `:${perm.page}` : '') + (perm.feature ? `:${perm.feature}` : '');
      if (!permissionMap.has(key)) {
        permissionMap.set(key, new Set());
      }
      for (const action of perm.actions) {
        permissionMap.get(key)!.add(action);
      }
    }

    // Apply grants (add)
    for (const grant of grants) {
      const key = grant.module + (grant.page ? `:${grant.page}` : '') + (grant.feature ? `:${grant.feature}` : '');
      if (!permissionMap.has(key)) {
        permissionMap.set(key, new Set());
      }
      for (const action of grant.actions) {
        permissionMap.get(key)!.add(action);
      }
    }

    // Apply denies (remove)
    for (const deny of denies) {
      const key = deny.module + (deny.page ? `:${deny.page}` : '') + (deny.feature ? `:${deny.feature}` : '');
      const denyActions: string[] = deny.actions.map((a: string) => a.startsWith('!') ? a.slice(1) : a);
      if (permissionMap.has(key)) {
        const actions = permissionMap.get(key)!;
        for (const action of denyActions) {
          actions.delete(action);
        }
        // Remove entry if no actions remain
        if (actions.size === 0) {
          permissionMap.delete(key);
        }
      }
    }

    // Convert to resolved permissions array
    const resolvedPermissions = Array.from(permissionMap.entries()).map(([key, actions]) => {
      const parts = key.split(':');
      const module = parts[0];
      const page = parts.length > 1 ? parts[1] : undefined;
      const feature = parts.length > 2 ? parts[2] : undefined;
      return {
        module,
        ...(page ? { page } : {}),
        ...(feature ? { feature } : {}),
        actions: Array.from(actions),
      };
    });

    res.json({
      userId: (user as any)._id || (user as any).id,
      role: user.role,
      roleId: (user as any).roleId || null,
      rolePermissions,
      overrides: override ? { grants, denies } : null,
      resolvedPermissions,
      isSuperAdmin: false,
      companyId,
    });
  })
);
export default orgAdminUsersRoutes;
