import {
  descripcionIncidencia,
  firmaIncidencia,
  motivoCargable,
  prioridadIncidencia,
  tituloIncidencia,
} from './decision';
import { IncidenciasService } from './incidencias.service';

describe('decisión de incidencias (la toma el código, no el modelo)', () => {
  it('solo los motivos accionables por Novasis crean incidencia', () => {
    for (const si of [
      'trabado_sifen',
      'falla_sifen_persistente',
      'sync_empresa_hub',
      'posible_error_sistema',
      'consulta_sin_respuesta_en_guias',
      'cliente_disconforme',
    ]) {
      expect(motivoCargable(si)).toBe(true);
    }
    for (const no of ['sin_permiso', 'no_encontrado', 'fuera_de_alcance', null, undefined, 'otro']) {
      expect(motivoCargable(no as string)).toBe(false);
    }
  });

  it('misma firma para el mismo problema, distinta para otro', () => {
    expect(firmaIncidencia('trabado_sifen', 'documento_sifen')).toBe('trabado_sifen|documento_sifen');
    expect(firmaIncidencia('trabado_sifen', null)).toBe('trabado_sifen|general');
    expect(firmaIncidencia('posible_error_sistema', 'datos_negocio')).not.toBe(
      firmaIncidencia('posible_error_sistema', 'documento_sifen'),
    );
  });

  it('prioridad según el motivo y título legible', () => {
    expect(prioridadIncidencia('trabado_sifen')).toBe('Critical');
    expect(prioridadIncidencia('cliente_disconforme')).toBe('Minor');
    expect(tituloIncidencia('posible_error_sistema', 'datos_negocio')).toBe(
      '[soporte-ia] Posible error del sistema · datos_negocio',
    );
  });

  it('la descripción lleva datos mínimos: nunca texto del cliente', () => {
    const d = descripcionIncidencia({
      motivo: 'trabado_sifen',
      categoria: 'documento_sifen',
      firma: 'trabado_sifen|documento_sifen',
      primera: { sop: 12, ruc: '80144315', razon_social: 'DOBA S.A.', fecha: '2026-10-26 19:00 UTC' },
    });
    expect(d).toContain('SOP-12');
    expect(d).toContain('80144315 DOBA S.A.');
    expect(d).not.toMatch(/mensaje|pregunta del cliente/i);
  });
});

type Fila = {
  id: string;
  firma: string;
  motivo: string;
  categoria: string | null;
  titulo: string;
  prioridad: string;
  estado: string;
  youtrack_id: string | null;
  ocurrencias: number;
  empresas: unknown;
  eventos: unknown;
  intentos: number;
  proximo_intento_at: Date | null;
  updated_at: Date;
};

/** Prisma de mentira: una tabla en memoria con lo que usa el servicio. */
function prismaDeMentira() {
  const filas: Fila[] = [];
  return {
    filas,
    incidencias: {
      findUnique: jest.fn(
        async ({ where }: { where: { firma: string } }) => filas.find((f) => f.firma === where.firma) ?? null,
      ),
      create: jest.fn(async ({ data }: { data: Partial<Fila> }) => {
        const fila = {
          id: `id-${filas.length + 1}`,
          estado: 'PENDIENTE',
          youtrack_id: null,
          ocurrencias: 1,
          intentos: 0,
          proximo_intento_at: null,
          updated_at: new Date(),
          categoria: null,
          ...data,
        } as Fila;
        filas.push(fila);
        return fila;
      }),
      update: jest.fn(
        async ({ where, data }: { where: { firma?: string; id?: string }; data: Record<string, unknown> }) => {
          const fila = filas.find((f) => (where.firma ? f.firma === where.firma : f.id === where.id));
          if (!fila) throw new Error('no existe');
          for (const [k, v] of Object.entries(data)) {
            if (k === 'ocurrencias' && typeof v === 'object' && v)
              fila.ocurrencias += (v as { increment: number }).increment;
            else (fila as Record<string, unknown>)[k] = v;
          }
          return fila;
        },
      ),
      // Aplica el mismo filtro que la consulta real: backoff y tope de intentos.
      findMany: jest.fn(async () =>
        filas.filter((f) => f.intentos < 12 && (!f.proximo_intento_at || f.proximo_intento_at <= new Date())),
      ),
    },
  };
}

describe('bandeja de incidencias', () => {
  const turno = {
    numero: 12,
    empresaRuc: '80144315',
    empresaRazonSocial: 'DOBA S.A.',
    motivo: 'trabado_sifen',
    categoria: 'documento_sifen',
  };

  it('encola una vez por problema: la segunda ocurrencia suma evento y empresa, no otra fila', async () => {
    const prisma = prismaDeMentira();
    const servicio = new IncidenciasService(prisma as never, { disponible: false } as never);
    await servicio.evaluar(turno);
    await servicio.evaluar({ ...turno, numero: 15, empresaRuc: '80012345' });
    await servicio.evaluar({ ...turno, motivo: 'sin_permiso' }); // no cargable
    expect(prisma.filas).toHaveLength(1);
    expect(prisma.filas[0].ocurrencias).toBe(2);
    expect(prisma.filas[0].empresas).toEqual(['80144315', '80012345']);
    expect((prisma.filas[0].eventos as unknown[]).length).toBe(2);
  });

  it('drenar crea el issue, pone la prioridad, comenta el resto y vacía los eventos', async () => {
    const prisma = prismaDeMentira();
    const youtrack = {
      disponible: true,
      crearIssue: jest.fn(async () => ({ id: 'x', idReadable: 'NE-7' })),
      comentar: jest.fn(async () => undefined),
      comando: jest.fn(async () => undefined),
    };
    const servicio = new IncidenciasService(prisma as never, youtrack as never);
    await servicio.evaluar(turno);
    await servicio.evaluar({ ...turno, numero: 15 });
    const r = await servicio.drenar();
    expect(r).toEqual({ creadas: 1, comentadas: 1, fallas: 0 });
    expect(youtrack.crearIssue).toHaveBeenCalledWith(
      '[soporte-ia] Documentos trabados en SIFEN · documento_sifen',
      expect.stringContaining('SOP-12'),
    );
    expect(youtrack.comando).toHaveBeenCalledWith('NE-7', 'Priority Critical');
    expect(youtrack.comentar).toHaveBeenCalledWith('NE-7', expect.stringContaining('SOP-15'));
    expect(prisma.filas[0]).toMatchObject({ estado: 'CREADA', youtrack_id: 'NE-7', eventos: [] });
    // Una ocurrencia posterior solo agrega un comentario.
    await servicio.evaluar({ ...turno, numero: 20 });
    const r2 = await servicio.drenar();
    expect(r2).toEqual({ creadas: 0, comentadas: 1, fallas: 0 });
  });

  it('si YouTrack falla, reintenta después y el error queda anotado', async () => {
    const prisma = prismaDeMentira();
    const youtrack = {
      disponible: true,
      crearIssue: jest.fn(async () => {
        throw new Error('YouTrack 502');
      }),
      comentar: jest.fn(),
      comando: jest.fn(),
    };
    const servicio = new IncidenciasService(prisma as never, youtrack as never);
    await servicio.evaluar(turno);
    const r = await servicio.drenar();
    expect(r.fallas).toBe(1);
    expect(prisma.filas[0]).toMatchObject({ estado: 'ERROR', intentos: 1, ultimo_error: 'YouTrack 502' });
    expect(prisma.filas[0].proximo_intento_at!.getTime()).toBeGreaterThan(Date.now());
    // Antes del próximo intento no vuelve a pegar a YouTrack.
    const r2 = await servicio.drenar();
    expect(r2).toEqual({ creadas: 0, comentadas: 0, fallas: 0 });
  });
});
