import * as mysql from "mysql2/promise";
import { TenantEstado } from "../control-plane/entities/tenant.entity";
import { TenantsAdminService } from "./tenants-admin.service";

jest.mock("mysql2/promise");

function createService(overrides: Record<string, any> = {}) {
  const tenant = {
    id: 42,
    codigo: "prueba",
    estado: TenantEstado.ONBOARDING,
    dbHost: "host",
    dbPort: 3306,
    dbUser: "user",
    dbPasswordEncrypted: "ENC(pass)",
    dbName: "db",
  };

  const tenantRecords = {
    findById: jest.fn(async () => tenant),
    updateEstado: jest.fn(async (id: number, estado: any) => ({
      ...tenant,
      estado,
    })),
    ...overrides.tenantRecords,
  };

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

  const schemaCheck = {
    getPendingMigrations: jest.fn(async () => []),
    getColumnDeviations: jest.fn(async () => []),
    ...overrides.schemaCheck,
  };

  const tallerParametrosService = {
    getTogglesOperativosAdmin: jest.fn(async () => ({
      permiteTiendas: true,
      permiteProductosPropios: true,
      facturaHabilitada: true,
    })),
    setExplicit: jest.fn(async () => undefined),
    setGrupoExplicit: jest.fn(async () => undefined),
    getBooleanosTenantExplicit: jest.fn(async () => ({
      "ot.usa_fotos": false,
      "ot.usa_danos": true,
      "facturacion.usa_facturacion": false,
      "facturacion.exige_pago_para_entrega": true,
      "notificaciones.usa_whatsapp": false,
    })),
    ...overrides.tallerParametrosService,
  };

  const service = new TenantsAdminService(
    tenantRecords as any,
    crypto as any,
    schemaCheck as any,
    {} as any, // identityService -- no usado en getResumenOperativo/activarTenant/updateTogglesOperativos
    {} as any, // tenantRegistry -- idem
    {} as any, // reconciliationService -- idem
    tallerParametrosService as any,
  );

  return {
    service,
    tenant,
    tenantRecords,
    crypto,
    schemaCheck,
    tallerParametrosService,
  };
}

describe("TenantsAdminService.getResumenOperativo", () => {
  it("lee los toggles vía TallerParametrosService, no TenantConfigService", async () => {
    const mockConn = {
      query: jest.fn(async () => [[{ c: 0 }]]),
      end: jest.fn(async () => undefined),
    };
    (mysql.createConnection as jest.Mock).mockResolvedValue(mockConn);

    const { service, tallerParametrosService } = createService();

    const resumen = await service.getResumenOperativo(42);

    expect(
      tallerParametrosService.getTogglesOperativosAdmin,
    ).toHaveBeenCalledWith(42);
    expect(resumen.toggles).toEqual({
      permiteTiendas: true,
      permiteProductosPropios: true,
      facturaHabilitada: true,
    });
  });
});

describe("TenantsAdminService.activarTenant", () => {
  it("un toggle en null (no se pudo calcular) bloquea igual que un conteo en null", async () => {
    const { service } = createService();
    jest.spyOn(service, "getEstadoDetallado").mockResolvedValue({
      tenant: {} as any,
      acceso: { ok: true },
      migracionesPendientes: [],
      desviaciones: [],
    });
    jest.spyOn(service, "getResumenOperativo").mockResolvedValue({
      tiendas: 0,
      tiendasConFacturacionPos: 5,
      productos: 5,
      toggles: {
        permiteTiendas: null,
        permiteProductosPropios: true,
        facturaHabilitada: true,
      },
    });

    await expect(service.activarTenant(42)).rejects.toThrow(
      /no hay ninguna tienda creada/,
    );
  });

  it("un sitio nuevo (todo apagado por defecto) se activa sin exigir tienda, productos ni facturación POS", async () => {
    const { service, tenantRecords } = createService();
    jest.spyOn(service, "getEstadoDetallado").mockResolvedValue({
      tenant: {} as any,
      acceso: { ok: true },
      migracionesPendientes: [],
      desviaciones: [],
    });
    jest.spyOn(service, "getResumenOperativo").mockResolvedValue({
      tiendas: 0,
      tiendasConFacturacionPos: 0,
      productos: 0,
      toggles: {
        permiteTiendas: false,
        permiteProductosPropios: false,
        facturaHabilitada: false,
      },
    });

    await expect(service.activarTenant(42)).resolves.toBeDefined();
    expect(tenantRecords.updateEstado).toHaveBeenCalledWith(
      42,
      TenantEstado.ACTIVO,
    );
  });

  it("un toggle en false SÍ se salta el chequeo correspondiente", async () => {
    const { service } = createService();
    jest.spyOn(service, "getEstadoDetallado").mockResolvedValue({
      tenant: {} as any,
      acceso: { ok: true },
      migracionesPendientes: [],
      desviaciones: [],
    });
    jest.spyOn(service, "getResumenOperativo").mockResolvedValue({
      tiendas: 0,
      tiendasConFacturacionPos: 5,
      productos: 5,
      toggles: {
        permiteTiendas: false,
        permiteProductosPropios: true,
        facturaHabilitada: true,
      },
    });

    await expect(service.activarTenant(42)).resolves.toBeDefined();
  });
});

describe("TenantsAdminService.updateTogglesOperativos", () => {
  it("escribe en taller_parametros vía setExplicit, con el actor plataforma (0), no en tenant_config", async () => {
    const { service, tallerParametrosService } = createService();

    await service.updateTogglesOperativos(42, { permiteTiendas: false });

    expect(tallerParametrosService.setExplicit).toHaveBeenCalledWith(
      42,
      "tiendas.permite_crear",
      false,
      0,
      { allowOnboarding: true },
    );
  });

  it("solo escribe los campos presentes en el DTO", async () => {
    const { service, tallerParametrosService } = createService();

    await service.updateTogglesOperativos(42, { facturaHabilitada: false });

    expect(tallerParametrosService.setExplicit).toHaveBeenCalledTimes(1);
    expect(tallerParametrosService.setExplicit).toHaveBeenCalledWith(
      42,
      "facturacion.usa_facturacion",
      false,
      0,
      { allowOnboarding: true },
    );
  });
});

describe("TenantsAdminService.getParametrosOperativos", () => {
  it("lista solo claves booleanas de ámbito tenant, con su sección, valor y default", async () => {
    const { service, tallerParametrosService } = createService();

    const { parametros } = await service.getParametrosOperativos(42);

    expect(
      tallerParametrosService.getBooleanosTenantExplicit,
    ).toHaveBeenCalledWith(42, { allowOnboarding: true });
    const claves = parametros.map((p) => p.clave);
    expect(claves).toContain("ot.usa_fotos");
    expect(claves).toContain("facturacion.usa_facturacion");
    expect(claves).not.toContain("notificaciones.whatsapp_api_version");
    expect(claves).not.toContain("facturacion_pos.habilitada");

    const fotos = parametros.find((p) => p.clave === "ot.usa_fotos")!;
    expect(fotos).toMatchObject({
      grupo: "operacion",
      seccion: "Evidencia",
      valor: false,
      valorPorDefecto: false,
    });
    const pago = parametros.find(
      (p) => p.clave === "facturacion.exige_pago_para_entrega",
    )!;
    expect(pago).toMatchObject({
      grupo: "facturacion",
      seccion: null,
      valor: true,
      valorPorDefecto: true,
    });
  });

  it("una clave sin valor devuelto se muestra apagada", async () => {
    const { service } = createService();

    const { parametros } = await service.getParametrosOperativos(42);

    expect(parametros.find((p) => p.clave === "ot.usa_mecanicos")!.valor).toBe(
      false,
    );
  });
});

describe("TenantsAdminService.updateParametrosOperativos", () => {
  it("escribe solo lo que cambió, en un solo lote, con actor plataforma (0) y allowOnboarding", async () => {
    const { service, tallerParametrosService } = createService();

    await service.updateParametrosOperativos(42, {
      valores: {
        "ot.usa_fotos": true,
        "ot.usa_danos": true,
        "facturacion.usa_facturacion": false,
      },
    });

    expect(tallerParametrosService.setGrupoExplicit).toHaveBeenCalledTimes(1);
    expect(tallerParametrosService.setGrupoExplicit).toHaveBeenCalledWith(
      42,
      { "ot.usa_fotos": true },
      0,
      { allowOnboarding: true },
    );
  });

  it("no escribe nada si ningún valor cambia", async () => {
    const { service, tallerParametrosService } = createService();

    await service.updateParametrosOperativos(42, {
      valores: { "ot.usa_danos": true },
    });

    expect(tallerParametrosService.setGrupoExplicit).not.toHaveBeenCalled();
  });

  it("rechaza una clave desconocida sin escribir nada", async () => {
    const { service, tallerParametrosService } = createService();

    await expect(
      service.updateParametrosOperativos(42, {
        valores: { "ot.usa_fotos": true, "ot.no_existe": true },
      }),
    ).rejects.toThrow(/desconocido/);
    expect(tallerParametrosService.setGrupoExplicit).not.toHaveBeenCalled();
  });

  it("rechaza una clave que no es booleana o no es de ámbito tenant", async () => {
    const { service, tallerParametrosService } = createService();

    await expect(
      service.updateParametrosOperativos(42, {
        valores: { "notificaciones.whatsapp_api_version": true },
      }),
    ).rejects.toThrow(/no es un parámetro booleano/);
    await expect(
      service.updateParametrosOperativos(42, {
        valores: { "facturacion_pos.habilitada": true },
      }),
    ).rejects.toThrow(/no es un parámetro booleano/);
    expect(tallerParametrosService.setGrupoExplicit).not.toHaveBeenCalled();
  });

  it("rechaza un valor que no es boolean (incluidos 'true' como texto y 1)", async () => {
    const { service, tallerParametrosService } = createService();

    await expect(
      service.updateParametrosOperativos(42, {
        valores: { "ot.usa_fotos": "true" },
      }),
    ).rejects.toThrow(/true o false/);
    await expect(
      service.updateParametrosOperativos(42, {
        valores: { "ot.usa_fotos": 1 },
      }),
    ).rejects.toThrow(/true o false/);
    expect(tallerParametrosService.setGrupoExplicit).not.toHaveBeenCalled();
  });

  it("rechaza nombres heredados del prototipo como clave", async () => {
    const { service } = createService();

    await expect(
      service.updateParametrosOperativos(42, {
        valores: { constructor: true },
      }),
    ).rejects.toThrow(/desconocido/);
  });

  it("devuelve el estado actualizado releyendo los valores", async () => {
    const { service, tallerParametrosService } = createService();

    await service.updateParametrosOperativos(42, {
      valores: { "ot.usa_fotos": true },
    });

    expect(
      tallerParametrosService.getBooleanosTenantExplicit,
    ).toHaveBeenCalledTimes(2);
  });

  it("el updateTogglesOperativos existente sigue escribiendo vía setExplicit", async () => {
    const { service, tallerParametrosService } = createService();

    await service.updateTogglesOperativos(42, {
      permiteProductosPropios: true,
    });

    expect(tallerParametrosService.setExplicit).toHaveBeenCalledWith(
      42,
      "productos.permite_crear",
      true,
      0,
      {
        allowOnboarding: true,
      },
    );
  });
});
