import { ForbiddenException, NotFoundException } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { RequiereFlag } from "./requiere-flag.decorator";
import { RequiereFlagGuard } from "./requiere-flag.guard";
import { TallerParametrosService } from "./taller-parametros.service";

@RequiereFlag("modulos.usa_historial")
class ControllerGateado {
  handler() {}
}

class ControllerMixto {
  @RequiereFlag("ot.usa_danos", { soloEscritura: true })
  handlerEscritura() {}

  @RequiereFlag("modulos.usa_cargas_masivas", { comoNoEncontrado: true })
  handlerPublico() {}

  sinFlag() {}
}

function contexto(clase: any, handler: string, metodo = "GET") {
  return {
    getHandler: () => clase.prototype[handler],
    getClass: () => clase,
    switchToHttp: () => ({ getRequest: () => ({ method: metodo }) }),
  } as any;
}

function crearGuard(flags: Record<string, boolean>) {
  const servicio = Object.create(TallerParametrosService.prototype) as TallerParametrosService;
  (servicio as any).getBooleanConFallback = jest.fn(async (clave: string) => flags[clave] ?? true);
  return { guard: new RequiereFlagGuard(new Reflector(), servicio), servicio };
}

describe("RequiereFlagGuard", () => {
  it("deja pasar cuando el flag esta encendido", async () => {
    const { guard } = crearGuard({ "modulos.usa_historial": true });

    await expect(guard.canActivate(contexto(ControllerGateado, "handler"))).resolves.toBe(true);
  });

  it("rechaza con 403 y un mensaje accionable cuando el flag esta apagado", async () => {
    const { guard } = crearGuard({ "modulos.usa_historial": false });

    const promesa = guard.canActivate(contexto(ControllerGateado, "handler"));

    await expect(promesa).rejects.toBeInstanceOf(ForbiddenException);
    await expect(promesa).rejects.toThrow(/no está habilitado para este taller.*Configuración/);
  });

  it("sin metadata es un no-op (ni siquiera lee el parametro)", async () => {
    const { guard, servicio } = crearGuard({});

    await expect(guard.canActivate(contexto(ControllerMixto, "sinFlag"))).resolves.toBe(true);
    expect((servicio as any).getBooleanConFallback).not.toHaveBeenCalled();
  });

  it("rutas publicas apagadas responden 404, no 403", async () => {
    const { guard } = crearGuard({ "modulos.usa_cargas_masivas": false });

    await expect(guard.canActivate(contexto(ControllerMixto, "handlerPublico"))).rejects.toBeInstanceOf(
      NotFoundException,
    );
  });

  describe("soloEscritura (catalogos que el sync offline sigue leyendo)", () => {
    it("deja pasar la lectura aunque el flag este apagado", async () => {
      const { guard } = crearGuard({ "ot.usa_danos": false });

      await expect(guard.canActivate(contexto(ControllerMixto, "handlerEscritura", "GET"))).resolves.toBe(true);
    });

    it.each(["POST", "PUT", "PATCH", "DELETE"])("rechaza %s con el flag apagado", async (metodo) => {
      const { guard } = crearGuard({ "ot.usa_danos": false });

      await expect(guard.canActivate(contexto(ControllerMixto, "handlerEscritura", metodo))).rejects.toBeInstanceOf(
        ForbiddenException,
      );
    });
  });

  it("si leer el parametro falla, aplica comportamientoAnteFallo (permitir): no se cae una funcion por un fallo de lectura", async () => {
    const servicio = Object.create(TallerParametrosService.prototype) as any;
    servicio.get = jest.fn(async () => {
      throw new Error("BD caida");
    });
    servicio.comportamiento = jest.fn(() => "permitir");
    servicio.logger = { warn: jest.fn() };
    const guard = new RequiereFlagGuard(new Reflector(), servicio);

    await expect(guard.canActivate(contexto(ControllerGateado, "handler"))).resolves.toBe(true);
  });
});
