import { Criteria } from "@repo/rdx-criteria/server";
import { NextFunction, Request, Response } from "express";
import { ApiError, ForbiddenApiError, UnauthorizedApiError, ValidationApiError } from "./errors";
import { ExpressController } from "./express-controller";

export type GuardResultLike = { isFailure: boolean; error?: ApiError };

export type GuardContext = {
  req: Request;
  res: Response;
  next: NextFunction;
  controller: ExpressController;
  criteria: Criteria;
};
export type GuardFn = (ctx: GuardContext) => GuardResultLike | Promise<GuardResultLike>;

export function guardOk(): GuardResultLike {
  return { isFailure: false };
}

export function guardFail(error: ApiError): GuardResultLike {
  return { isFailure: true, error };
}

// ───────────────────────────────────────────────────────────────────────────
// Guards reutilizables (auth/tenancy). Si prefieres, muévelos a src/lib/http/express-guards.ts

export function authGuard(): GuardFn {
  return ({ controller }: GuardContext) => {
    const user = controller.getUser();
    if (!user) return guardFail(new UnauthorizedApiError("Unauthorized"));
    return guardOk();
  };
}

export function tenantGuard(): GuardFn {
  return ({ controller }: GuardContext) => {
    const tenantId = controller.getTenantId();
    if (!tenantId) return guardFail(new ForbiddenApiError("Tenant not found for user"));
    return guardOk();
  };
}

/**
  Impide que el cliente intente fijar el 'companyId' vía query (?companyId=...).
  La política de scoping real debe hacerse en repos/servicios; esto es defensa adicional.
*/
export function forbidQueryFieldGuard(field = "companyId"): GuardFn {
  return ({ req }: GuardContext) => {
    if (Object.prototype.hasOwnProperty.call(req.query, field)) {
      return guardFail(new ValidationApiError(`Query param "${field}" is not allowed`));
    }
    return guardOk();
  };
}

/**
  Inyecta el tenant del usuario en el body (create/update).
  Ignora cualquier valor que venga del cliente.
*/
export function injectTenantIntoBodyGuard(field = "companyId"): GuardFn {
  return ({ controller }: GuardContext) => {
    const tenantId = controller.getTenantId();
    if (!tenantId) return guardFail(new ForbiddenApiError("Tenant not found for user"));

    const body = (controller as any).req.body ?? {};
    (controller as any).req.body = { ...body, [field]: tenantId };
    return guardOk();
  };
}
