import { NotFoundException } from "@nestjs/common";
import { AuthService } from "./auth.service";

function createService(overrides: Record<string, any> = {}) {
  const tiendaExistente = {
    id: 3,
    codigo: "T3",
    nombre: "Tienda 3",
    descripcion: null,
    direccion: null,
    nombreImpresion: null,
    direccionImpresion: null,
    telefonoImpresion: null,
    logoUrl: null,
    facturacionPosHabilitada: false,
    facturacionPosUrl: null,
    facturacionPosUsername: null,
    facturacionPosPasswordEncrypted: null,
    facturacionPosComputadora: null,
    facturacionPosPrecioDecimales: null,
  };

  let estadoActual = { ...tiendaExistente };
  const queryBuilder = {
    select: jest.fn().mockReturnThis(),
    getRawOne: jest.fn(async () => ({ max: 3 })),
  };
  const tiendaRepo = {
    findOne: jest.fn(async () => estadoActual),
    update: jest.fn(async (_id: number, partial: Record<string, any>) => {
      estadoActual = { ...estadoActual, ...partial };
    }),
    createQueryBuilder: jest.fn(() => queryBuilder),
    create: jest.fn((data: any) => data),
    save: jest.fn(async (data: any) => data),
    ...overrides.tiendaRepo,
  };

  const usuarioRepo = {
    findOne: jest.fn(async () => null),
    save: jest.fn(async (u: any) => u),
    ...overrides.usuarioRepo,
  };

  const tenantRegistry = {
    withTenantRepositoryExplicit: jest.fn(async (_tenantId: number, entity: any, fn: any) =>
      fn(entity?.name === "Tienda" ? tiendaRepo : usuarioRepo),
    ),
    ...overrides.tenantRegistry,
  };

  const identityService = {
    findActiveMembershipByUsuarioLocal: jest.fn(async () => null),
    findOrCreateIdentidadParaProvisioning: jest.fn(async () => ({ id: 99 })),
    createMembership: jest.fn(async () => ({ id: 1, identidadId: 99 })),
    updateCredenciales: jest.fn(async () => ({})),
    ...overrides.identityService,
  };

  const credentialsCrypto = {
    encrypt: jest.fn((plain: string) => `ENC(${plain})`),
    decrypt: jest.fn((blob: string) => blob.replace(/^ENC\(|\)$/g, "")),
    ...overrides.credentialsCrypto,
  };

  const tallerParametros = {
    getBooleanConFallbackExplicit: jest.fn(async () => true),
    setGrupoParaTiendaExplicit: jest.fn(async () => undefined),
    ...overrides.tallerParametros,
  };

  const service = new AuthService(
    tenantRegistry as any,
    identityService as any,
    {} as any,
    credentialsCrypto as any,
    tallerParametros as any,
  );

  return {
    service,
    tiendaRepo,
    usuarioRepo,
    tenantRegistry,
    identityService,
    credentialsCrypto,
    tallerParametros,
  };
}

describe("AuthService.updateTiendaFacturacionPos", () => {
  it("delega la escritura completa a TallerParametrosService.setGrupoParaTiendaExplicit", async () => {
    const tallerParametros = { setGrupoParaTiendaExplicit: jest.fn(async () => undefined) };
    const { service } = createService({ tallerParametros });

    const result = await service.updateTiendaFacturacionPos(
      42,
      3,
      {
        habilitada: true,
        url: "http://postouch/create_ordenes_edngt",
        username: "22",
        password: "clave-nueva",
        computadora: "5.5",
        precioDecimales: 2,
      },
      99,
    );

    expect(tallerParametros.setGrupoParaTiendaExplicit).toHaveBeenCalledWith(
      42,
      3,
      {
        "facturacion_pos.habilitada": true,
        "facturacion_pos.url": "http://postouch/create_ordenes_edngt",
        "facturacion_pos.username": "22",
        "facturacion_pos.password": "clave-nueva",
        "facturacion_pos.computadora": "5.5",
        "facturacion_pos.precio_decimales": 2,
      },
      99,
    );
    expect(result).toEqual({ tiendaId: 3, habilitada: true });
  });

  it("lanza NotFoundException si la tienda no existe", async () => {
    const { service } = createService({
      tiendaRepo: { findOne: jest.fn(async () => null) },
    });

    await expect(
      service.updateTiendaFacturacionPos(42, 999, { habilitada: false }, 99),
    ).rejects.toThrow(NotFoundException);
  });

  it("no incluye facturacion_pos.password en el grupo si no se manda una contraseña nueva", async () => {
    const tallerParametros = { setGrupoParaTiendaExplicit: jest.fn(async () => undefined) };
    const { service } = createService({ tallerParametros });

    await service.updateTiendaFacturacionPos(42, 3, { habilitada: false }, 99);

    expect(tallerParametros.setGrupoParaTiendaExplicit).toHaveBeenCalledWith(
      42,
      3,
      { "facturacion_pos.habilitada": false },
      99,
    );
  });
});

describe("AuthService.updateUser", () => {
  const usuarioExistente = {
    id: 6,
    username: "correoprueba1",
    email: "correoprueba1@gmail.com",
    passwordHash: "hash-viejo",
    nombre: "Prueba Uno",
    rol: "operativo",
    permisos: {},
    tiendaId: null,
    activo: true,
    ultimoLogin: null,
    createdAt: new Date(),
  };

  it("repara la membership faltante (usuario legacy) y propaga la contrasena nueva", async () => {
    const { service, identityService, usuarioRepo } = createService({
      usuarioRepo: { findOne: jest.fn(async () => ({ ...usuarioExistente })) },
    });

    await service.updateUser(42, 6, { password: "clave-nueva" }, 1);

    expect(identityService.findOrCreateIdentidadParaProvisioning).toHaveBeenCalledWith(
      "correoprueba1@gmail.com",
      "Prueba Uno",
      expect.any(String),
    );
    expect(identityService.createMembership).toHaveBeenCalledWith(
      expect.objectContaining({ identidadId: 99, tenantId: 42, usuarioLocalId: 6, otorgadoPorIdentidadId: 1 }),
    );
    expect(identityService.updateCredenciales).toHaveBeenCalledWith(
      99,
      expect.objectContaining({ passwordHash: expect.any(String) }),
    );
    expect(usuarioRepo.save).toHaveBeenCalled();
  });

  it("si ya hay membership activa, no vuelve a crear identidad", async () => {
    const { service, identityService } = createService({
      usuarioRepo: { findOne: jest.fn(async () => ({ ...usuarioExistente })) },
      identityService: {
        findActiveMembershipByUsuarioLocal: jest.fn(async () => ({ id: 5, identidadId: 77 })),
      },
    });

    await service.updateUser(42, 6, { password: "clave-nueva" }, 1);

    expect(identityService.findOrCreateIdentidadParaProvisioning).not.toHaveBeenCalled();
    expect(identityService.updateCredenciales).toHaveBeenCalledWith(
      77,
      expect.objectContaining({ passwordHash: expect.any(String) }),
    );
  });
});

describe("AuthService.createTienda", () => {
  it("asigna el siguiente id correlativo y marca creadaEnTaller", async () => {
    const { service, tiendaRepo } = createService();

    const result = await service.createTienda(42, { nombre: "Tienda Nueva" });

    expect(tiendaRepo.save).toHaveBeenCalledWith(
      expect.objectContaining({
        id: 4,
        nombre: "Tienda Nueva",
        status: 0,
        alta: "S",
        esTransito: "N",
        creadaEnTaller: true,
      }),
    );
    expect(result.creadaEnTaller).toBe(true);
  });

  it("usa id 1 si todavia no hay ninguna tienda", async () => {
    const { service, tiendaRepo } = createService({
      tiendaRepo: {
        createQueryBuilder: jest.fn(() => ({
          select: jest.fn().mockReturnThis(),
          getRawOne: jest.fn(async () => ({ max: null })),
        })),
        create: jest.fn((data: any) => data),
        save: jest.fn(async (data: any) => data),
      },
    });

    await service.createTienda(42, { nombre: "Primera tienda" });

    expect(tiendaRepo.save).toHaveBeenCalledWith(expect.objectContaining({ id: 1 }));
  });
});

describe("AuthService.createTienda", () => {
  it("rechaza con ForbiddenException si tiendas.permite_crear está en false", async () => {
    const tallerParametros = { getBooleanConFallbackExplicit: jest.fn(async () => false) };
    const { service } = createService({ tallerParametros });

    await expect(
      service.createTienda(42, { nombre: "Sucursal nueva" }),
    ).rejects.toThrow(/no permite crear tiendas/);
    expect(tallerParametros.getBooleanConFallbackExplicit).toHaveBeenCalledWith(42, "tiendas.permite_crear");
  });

  it("crea la tienda normalmente cuando tiendas.permite_crear está en true", async () => {
    const tallerParametros = { getBooleanConFallbackExplicit: jest.fn(async () => true) };
    const { service, tiendaRepo } = createService({ tallerParametros });

    await service.createTienda(42, { nombre: "Sucursal nueva" });

    expect(tiendaRepo.save).toHaveBeenCalled();
  });
});
