import { BadRequestException, ForbiddenException } from "@nestjs/common";
import { TallerParametrosController } from "./taller-parametros.controller";

function createController(overrides: Record<string, any> = {}) {
  const tallerParametrosService = {
    getResumenTenant: jest.fn(async () => ({ tenant: {}, tiendas: [] })),
    getFlags: jest.fn(async () => ({
      usaFacturacion: true,
      permiteTiendas: true,
      permiteProductosPropios: true,
      usaWhatsapp: false,
    })),
    setGrupo: jest.fn(async () => undefined),
    getParaTienda: jest.fn(async (_id: number, clave: string) =>
      clave === "facturacion_pos.url" ? "http://postouch" : "clave-guardada",
    ),
    ...overrides.tallerParametrosService,
  };
  const postouchClientService = {
    probarConexion: jest.fn(async () => ({ ok: true })),
    ...overrides.postouchClientService,
  };

  const controller = new TallerParametrosController(tallerParametrosService as any, postouchClientService as any);
  return { controller, tallerParametrosService, postouchClientService };
}

// Usuario con escritura en TODOS los grupos de configuracion y sin
// restricción de tienda -- el caso feliz que usan la mayoría de los tests
// de abajo. permisosOverride pisa puntualmente uno o más grupos (ej.
// { "configuracion.facturacion": "read" }); facturacionPosTiendaIds se
// asigna aparte, directo sobre req.user, cuando un test lo necesita.
function reqConPermisos(permisosOverride: Record<string, any> = {}) {
  return {
    user: {
      usuarioLocalId: 42,
      permissions: {
        "configuracion.facturacion": "write",
        "configuracion.inventario": "write",
        "configuracion.productos": "write",
        "configuracion.tiendas": "write",
        "configuracion.notificaciones": "write",
        ...permisosOverride,
      },
      facturacionPosTiendaIds: null as number[] | null,
    },
  };
}

describe("TallerParametrosController", () => {
  it("getCatalogo() devuelve el catálogo completo, sin llamar al servicio", () => {
    const { controller, tallerParametrosService } = createController();
    const catalogo = controller.getCatalogo();
    expect(catalogo["facturacion.usa_facturacion"]).toBeDefined();
    expect(tallerParametrosService.getResumenTenant).not.toHaveBeenCalled();
  });

  describe("getResumen()", () => {
    it("rechaza si el usuario no tiene acceso a ningún grupo de configuración", async () => {
      const { controller } = createController();
      const req = reqConPermisos({
        "configuracion.facturacion": "none",
        "configuracion.inventario": "none",
        "configuracion.productos": "none",
        "configuracion.tiendas": "none",
        "configuracion.notificaciones": "none",
      });

      await expect(controller.getResumen(req)).rejects.toThrow(ForbiddenException);
    });

    it("llama al servicio con predicados de lectura por grupo cuando tiene al menos un grupo", async () => {
      const { controller, tallerParametrosService } = createController();
      const req = reqConPermisos({
        "configuracion.facturacion": "none",
        "configuracion.inventario": "none",
        "configuracion.productos": "none",
        "configuracion.tiendas": "none",
        "configuracion.notificaciones": "read",
      });

      await controller.getResumen(req);

      expect(tallerParametrosService.getResumenTenant).toHaveBeenCalledTimes(1);
      const filtro = tallerParametrosService.getResumenTenant.mock.calls[0][0];
      expect(filtro.puedeLeerGrupo("notificaciones")).toBe(true);
      expect(filtro.puedeLeerGrupo("facturacion")).toBe(false);
    });
  });

  describe("actualizarTenant()", () => {
    it("mapea el DTO y llama a setGrupo con el usuario actor del request", async () => {
      const { controller, tallerParametrosService } = createController();
      const req = reqConPermisos();

      await controller.actualizarTenant({ usaFacturacion: false }, req);

      expect(tallerParametrosService.setGrupo).toHaveBeenCalledWith({ "facturacion.usa_facturacion": false }, 42);
    });

    it("rechaza si el usuario no tiene escritura en el grupo de la clave enviada", async () => {
      const { controller, tallerParametrosService } = createController();
      const req = reqConPermisos({ "configuracion.facturacion": "read" });

      await expect(
        controller.actualizarTenant({ usaFacturacion: false }, req),
      ).rejects.toThrow(ForbiddenException);
      expect(tallerParametrosService.setGrupo).not.toHaveBeenCalled();
    });
  });

  describe("probarConexionPos()", () => {
    it("usa la contraseña del request si viene", async () => {
      const { controller, postouchClientService, tallerParametrosService } = createController();

      await controller.probarConexionPos(
        3,
        { url: "http://postouch", username: "22", password: "clave-del-form", computadora: "5.5" },
        reqConPermisos(),
      );

      expect(postouchClientService.probarConexion).toHaveBeenCalledWith({
        url: "http://postouch",
        username: "22",
        password: "clave-del-form",
        computadora: "5.5",
      });
      expect(tallerParametrosService.getParaTienda).not.toHaveBeenCalled();
    });

    it("usa la contraseña guardada si el request no manda una", async () => {
      const { controller, postouchClientService, tallerParametrosService } = createController();

      await controller.probarConexionPos(
        3,
        { url: "http://postouch", username: "22", computadora: "5.5" },
        reqConPermisos(),
      );

      expect(tallerParametrosService.getParaTienda).toHaveBeenCalledWith(3, "facturacion_pos.password");
      expect(postouchClientService.probarConexion).toHaveBeenCalledWith(
        expect.objectContaining({ password: "clave-guardada" }),
      );
    });

    it("rechaza si no hay contraseña ni en el request ni guardada", async () => {
      const { controller } = createController({
        tallerParametrosService: {
          getParaTienda: jest.fn(async (_id: number, clave: string) =>
            clave === "facturacion_pos.url" ? "http://postouch" : "",
          ),
        },
      });

      await expect(
        controller.probarConexionPos(
          3,
          { url: "http://postouch", username: "22", computadora: "5.5" },
          reqConPermisos(),
        ),
      ).rejects.toThrow(BadRequestException);
    });

    it("rechaza probar la contraseña guardada contra una URL distinta a la guardada", async () => {
      const { controller, postouchClientService, tallerParametrosService } = createController();

      await expect(
        controller.probarConexionPos(
          3,
          { url: "http://atacante.example", username: "22", computadora: "5.5" },
          reqConPermisos(),
        ),
      ).rejects.toThrow(BadRequestException);

      expect(tallerParametrosService.getParaTienda).toHaveBeenCalledWith(3, "facturacion_pos.url");
      expect(tallerParametrosService.getParaTienda).not.toHaveBeenCalledWith(3, "facturacion_pos.password");
      expect(postouchClientService.probarConexion).not.toHaveBeenCalled();
    });

    it("rechaza si el usuario no tiene escritura en configuracion.facturacion", async () => {
      const { controller, postouchClientService } = createController();
      const req = reqConPermisos({ "configuracion.facturacion": "read" });

      await expect(
        controller.probarConexionPos(
          3,
          { url: "http://postouch", username: "22", password: "x", computadora: "5.5" },
          req,
        ),
      ).rejects.toThrow(ForbiddenException);
      expect(postouchClientService.probarConexion).not.toHaveBeenCalled();
    });

    it("rechaza si la tienda no está en la restricción facturacionPosTiendaIds del usuario", async () => {
      const { controller, postouchClientService } = createController();
      const req = reqConPermisos();
      req.user.facturacionPosTiendaIds = [7, 8];

      await expect(
        controller.probarConexionPos(
          3,
          { url: "http://postouch", username: "22", password: "x", computadora: "5.5" },
          req,
        ),
      ).rejects.toThrow(ForbiddenException);
      expect(postouchClientService.probarConexion).not.toHaveBeenCalled();
    });
  });
});
