import { randomBytes, timingSafeEqual } from 'node:crypto';
import type { NextFunction, Request, Response } from 'express';
import type { Database } from '../db/types.js';
import { ForbiddenError } from '../domain/errors.js';
import { getAuthorization } from '../services/rbac.js';

export function ensureCsrf(req: Request, res: Response, next: NextFunction): void {
  req.session.csrfToken ??= randomBytes(32).toString('base64url');
  res.locals.csrfToken = req.session.csrfToken;
  next();
}

export function csrfProtection(req: Request, _res: Response, next: NextFunction): void {
  if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return next();
  const supplied = String(req.get('X-CSRF-Token') ?? req.body?._csrf ?? '');
  const expected = req.session.csrfToken ?? '';
  const a = Buffer.from(supplied);
  const b = Buffer.from(expected);
  if (!a.length || a.length !== b.length || !timingSafeEqual(a, b)) return next(new ForbiddenError('نشانه امنیتی فرم منقضی یا نامعتبر است. صفحه را تازه کنید.'));
  next();
}

export function loadCurrentUser(db: Database) {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      res.locals.currentPath = req.path;
      res.locals.flash = req.session.flash;
      delete req.session.flash;
      if (!req.session.userId) return next();
      const result = await db.query<{
        id: string; name: string; username: string; warehouse_id: string | null; force_password_change: boolean;
        session_version: number; active: boolean; archived_at: Date | null;
      }>(
        `SELECT id, name, username, warehouse_id, force_password_change, session_version, active, archived_at
         FROM users WHERE id = $1`, [req.session.userId]
      );
      const row = result.rows[0];
      if (!row || !row.active || row.archived_at || row.session_version !== req.session.sessionVersion) {
        req.session.destroy(() => undefined);
        return next();
      }
      const auth = await getAuthorization(db, row.id);
      req.currentUser = {
        id: row.id, name: row.name, username: row.username, warehouseId: row.warehouse_id,
        forcePasswordChange: row.force_password_change, sessionVersion: row.session_version,
        roles: auth.roles, permissions: auth.permissions
      };
      res.locals.user = req.currentUser;
      res.locals.permissions = auth.permissions;
      next();
    } catch (error) { next(error); }
  };
}

export function requireAuth(req: Request, res: Response, next: NextFunction): void {
  if (!req.currentUser) return res.redirect('/login');
  if (req.currentUser.forcePasswordChange && !['/password/force', '/logout'].includes(req.path)) return res.redirect('/password/force');
  next();
}

export function requirePermission(permission: string) {
  return (req: Request, _res: Response, next: NextFunction): void => {
    if (!req.currentUser) return next(new ForbiddenError());
    if (!req.currentUser.permissions.has(permission)) return next(new ForbiddenError());
    next();
  };
}

export function setFlash(req: Request, kind: 'success' | 'error' | 'info', message: string): void {
  req.session.flash = { kind, message };
}

export function auditContext(req: Request) {
  return {
    actorId: req.currentUser?.id ?? null,
    actorRole: req.currentUser?.roles.join(',') ?? null,
    ip: req.ip ?? null,
    userAgent: req.get('user-agent') ?? null
  };
}
