import { CatalogosController } from "../catalogos/catalogos.controller";
import { PublicCotizacionesController } from "../cotizaciones/public-cotizaciones.controller";
import { HistorialController } from "../historial/historial.controller";
import { MecanicosController } from "../mecanicos/mecanicos.controller";
import { OtsController } from "../ots/ots.controller";
import { ReportesController } from "../reportes/reportes.controller";
import { PublicOtStatusController } from "../whatsapp/whatsapp.controller";
import { REQUIERE_FLAG_METADATA } from "./requiere-flag.decorator";
import { RequiereFlagGuard } from "./requiere-flag.guard";

// Apagar una funcion tiene que rechazarse en el backend. Este test existe
// porque un @RequiereFlag olvidado (o un guard que no esta en @UseGuards) no
// falla en ningun otro lado: la ruta simplemente queda abierta.
const guardsDe = (clase: any): unknown[] => Reflect.getMetadata("__guards__", clase) ?? [];
const metaDe = (destino: any) => Reflect.getMetadata(REQUIERE_FLAG_METADATA, destino);

describe("gating de funciones en controladores", () => {
  it.each([
    [HistorialController, "modulos.usa_historial"],
    [ReportesController, "modulos.usa_reportes"],
    [MecanicosController, "ot.usa_mecanicos"],
    [PublicCotizacionesController, "cotizacion.usa_link_publico"],
    [PublicOtStatusController, "ot.usa_estado_publico"],
  ])("%p tiene su flag a nivel de clase y registra el guard", (controller: any, clave) => {
    expect(metaDe(controller)).toEqual(expect.objectContaining({ clave }));
    expect(guardsDe(controller)).toContain(RequiereFlagGuard);
  });

  it("las rutas publicas apagadas responden 404", () => {
    expect(metaDe(PublicCotizacionesController).comoNoEncontrado).toBe(true);
    expect(metaDe(PublicOtStatusController).comoNoEncontrado).toBe(true);
  });

  it("el guard va DESPUES de los que resuelven el tenant (necesita su contexto)", () => {
    for (const controller of [HistorialController, ReportesController, MecanicosController, OtsController, CatalogosController]) {
      const guards = guardsDe(controller);
      expect(guards.indexOf(RequiereFlagGuard)).toBe(guards.length - 1);
    }
    for (const controller of [PublicCotizacionesController, PublicOtStatusController]) {
      const guards: any[] = guardsDe(controller);
      expect(guards[guards.length - 1]).toBe(RequiereFlagGuard);
      expect(guards.map((g) => g.name)).toContain("PublicTenantGuard");
    }
  });

  it("mecanicos solo bloquea escritura (la asignacion historica se sigue consultando)", () => {
    expect(metaDe(MecanicosController).soloEscritura).toBe(true);
  });

  it("la bitacora de la OT se gatea solo en la vista", () => {
    expect(metaDe(OtsController.prototype.getEventos)).toEqual(
      expect.objectContaining({ clave: "modulos.usa_bitacora" }),
    );
  });

  describe("catalogos", () => {
    const proto: any = CatalogosController.prototype;
    const metodos = Object.getOwnPropertyNames(proto).filter((n) => n !== "constructor");

    it("cada handler de escritura de cosas-trae / tipos-dano / cargas masivas tiene su flag", () => {
      const claves = metodos
        .map((m) => ({ m, meta: metaDe(proto[m]) }))
        .filter((x) => x.meta);

      const porClave = (c: string) => claves.filter((x) => x.meta.clave === c).map((x) => x.m);
      expect(porClave("ot.usa_cosas_trae")).toHaveLength(4);
      expect(porClave("ot.usa_danos")).toHaveLength(4);
      expect(porClave("modulos.usa_cargas_masivas")).toHaveLength(2);
      // cosas-trae y tipos-dano: solo escritura (el sync offline sigue leyendolos).
      for (const x of claves.filter((y) => y.meta.clave !== "modulos.usa_cargas_masivas")) {
        expect(x.meta.soloEscritura).toBe(true);
      }
    });

    it("los catalogos de vehiculos NO se gatean (son basicos de la OT)", () => {
      const gateados = metodos.filter((m) => metaDe(proto[m])).length;
      expect(gateados).toBe(10);
    });
  });
});
