/**
 * گاردهای Fastify: احراز هویت، بررسی مجوز و CSRF.
 * تمام تصمیم‌های امنیتی در سرور گرفته می‌شوند؛ هیچ اتکایی به Frontend وجود ندارد.
 */
import type { FastifyReply, FastifyRequest } from 'fastify';
import { config } from '../config/index.js';
import { Errors } from '../common/errors.js';
import { resolvePrincipal } from '../modules/auth/service.js';
import { can, type Principal, type ScopeRef } from '../modules/rbac/service.js';
import type { PermissionKey } from '../modules/rbac/catalog.js';

declare module 'fastify' {
  interface FastifyRequest {
    principal: Principal | null;
    sessionToken?: string;
  }
}

/** hook سراسری: هویت کاربر را از کوکی نشست استخراج می‌کند (بدون اجبار به ورود) */
export async function attachPrincipal(req: FastifyRequest) {
  const token = (req.cookies as Record<string, string | undefined>)?.[config.security.cookieName];
  req.sessionToken = token;
  req.principal = await resolvePrincipal(token);
}

export function requireAuth(req: FastifyRequest): Principal {
  if (!req.principal) throw Errors.unauthorized();
  return req.principal;
}

export function requirePermission(permission: PermissionKey | string, scope?: ScopeRef) {
  return (req: FastifyRequest) => {
    const principal = requireAuth(req);
    if (!can(principal, permission, scope)) throw Errors.forbidden();
    return principal;
  };
}

/** بررسی مجوز با دامنه‌ای که در زمان اجرا از روی موجودیت هدف محاسبه می‌شود */
export function assertPermission(req: FastifyRequest, permission: PermissionKey | string, scope?: ScopeRef): Principal {
  const principal = requireAuth(req);
  if (!can(principal, permission, scope)) throw Errors.forbidden();
  return principal;
}

const UNSAFE = new Set(['POST', 'PUT', 'PATCH', 'DELETE']);

/**
 * محافظت CSRF با الگوی Double Submit Cookie:
 * مقدار هدر X-CSRF-Token باید با کوکی غیر-HttpOnly مطابقت داشته باشد.
 */
export async function csrfGuard(req: FastifyRequest, _reply: FastifyReply) {
  if (!UNSAFE.has(req.method)) return;
  if (req.url.startsWith('/api/auth/login') || req.url.startsWith('/api/auth/register')) return;
  const cookieToken = (req.cookies as Record<string, string | undefined>)?.[config.security.csrfCookieName];
  const headerToken = req.headers['x-csrf-token'];
  if (!cookieToken || !headerToken || cookieToken !== headerToken) {
    throw Errors.forbidden('توکن CSRF نامعتبر است.');
  }
}
