import * as XLSX from 'xlsx';
import { MarcacionesParserService } from './marcaciones-parser.service';

function csv(rows: string[]): Buffer {
  return Buffer.from(rows.join('\n'), 'utf8');
}

function xlsx(headers: string[], data: (string | number)[][]): Buffer {
  const wb = XLSX.utils.book_new();
  const ws = XLSX.utils.aoa_to_sheet([headers, ...data]);
  XLSX.utils.book_append_sheet(wb, ws, 'Hoja1');
  return XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' });
}

describe('MarcacionesParserService', () => {
  let parser: MarcacionesParserService;

  beforeEach(() => {
    parser = new MarcacionesParserService();
  });

  describe('Formato A — fila por día', () => {
    it('parsea CSV con entrada y salida', () => {
      const buf = csv([
        'documento_funcionario,fecha,hora_entrada,hora_salida',
        '4567890,2026-05-04,08:00,17:00',
        '4567891,2026-05-04,08:15,17:05',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.formato).toBe('POR_DIA');
      expect(res.errores).toEqual([]);
      expect(res.marcaciones).toHaveLength(4);
      const m1 = res.marcaciones[0];
      expect(m1.documento_raw).toBe('4567890');
      expect(m1.tipo_marcacion).toBe('ENTRADA');
      expect(m1.timestamp_marcacion.toISOString()).toBe('2026-05-04T08:00:00.000Z');
    });

    it('genera sólo una marcación si hay sólo hora_entrada', () => {
      const buf = csv([
        'documento_funcionario,fecha,hora_entrada,hora_salida',
        '1234567,2026-05-04,08:00,',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.marcaciones).toHaveLength(1);
      expect(res.marcaciones[0].tipo_marcacion).toBe('ENTRADA');
    });

    it('reporta error si hora_salida es anterior o igual a hora_entrada', () => {
      const buf = csv([
        'documento_funcionario,fecha,hora_entrada,hora_salida',
        '1234567,2026-05-04,17:00,08:00',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.errores).toHaveLength(1);
      expect(res.errores[0].mensaje).toMatch(/hora_salida.*hora_entrada/i);
      expect(res.marcaciones).toEqual([]);
    });

    it('reporta error y NO aborta el resto del lote ante CI inválido', () => {
      const buf = csv([
        'documento_funcionario,fecha,hora_entrada,hora_salida',
        'abc,2026-05-04,08:00,17:00',
        '1234567,2026-05-04,08:15,17:05',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.errores).toHaveLength(1);
      expect(res.errores[0].mensaje).toMatch(/inválido/);
      expect(res.marcaciones).toHaveLength(2);
      expect(res.marcaciones[0].documento_raw).toBe('1234567');
    });

    it('exige CI de 5 a 9 dígitos', () => {
      const buf = csv([
        'documento_funcionario,fecha,hora_entrada,hora_salida',
        '1234,2026-05-04,08:00,17:00',
        '1234567890,2026-05-04,08:00,17:00',
        '12345,2026-05-04,08:00,17:00',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.errores).toHaveLength(2);
      expect(res.marcaciones).toHaveLength(2);
      expect(res.marcaciones[0].documento_raw).toBe('12345');
    });

    it('acepta fechas en formato DD/MM/YYYY', () => {
      const buf = csv([
        'documento_funcionario,fecha,hora_entrada,hora_salida',
        '1234567,04/05/2026,08:00,17:00',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.errores).toEqual([]);
      expect(res.marcaciones[0].timestamp_marcacion.toISOString()).toBe(
        '2026-05-04T08:00:00.000Z',
      );
    });

    it('limpia puntos y guiones del documento', () => {
      const buf = csv([
        'documento_funcionario,fecha,hora_entrada,hora_salida',
        '1.234.567,2026-05-04,08:00,17:00',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.errores).toEqual([]);
      expect(res.marcaciones[0].documento_raw).toBe('1234567');
    });

    it('acepta encabezados alternativos (cedula / entrada / salida)', () => {
      const buf = csv([
        'cedula,fecha,entrada,salida',
        '1234567,2026-05-04,08:00,17:00',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.errores).toEqual([]);
      expect(res.marcaciones).toHaveLength(2);
    });
  });

  describe('Formato B — marcación individual', () => {
    it('parsea CSV de marcaciones individuales', () => {
      const buf = csv([
        'documento_funcionario,timestamp_marcacion',
        '1234567,2026-05-04T08:00:00Z',
        '1234567,2026-05-04T17:05:00Z',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.formato).toBe('POR_MARCACION');
      expect(res.errores).toEqual([]);
      expect(res.marcaciones).toHaveLength(2);
      expect(res.marcaciones[0].tipo_marcacion).toBeUndefined();
    });

    it('reporta error si timestamp es inválido', () => {
      const buf = csv([
        'documento_funcionario,timestamp_marcacion',
        '1234567,no-es-fecha',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.errores).toHaveLength(1);
      expect(res.errores[0].mensaje).toMatch(/timestamp_marcacion inv/i);
    });

    it('parsea fechas tipo DD/MM/YYYY HH:MM', () => {
      const buf = csv([
        'documento_funcionario,timestamp_marcacion',
        '1234567,04/05/2026 08:00:00',
      ]);
      const res = parser.parseBuffer(buf);
      expect(res.errores).toEqual([]);
      expect(res.marcaciones[0].timestamp_marcacion.toISOString()).toBe(
        '2026-05-04T08:00:00.000Z',
      );
    });
  });

  describe('Casos de error globales', () => {
    it('lanza BadRequest si el archivo no tiene encabezados de marcación', () => {
      const buf = csv(['nombre,edad', 'Juan,30']);
      expect(() => parser.parseBuffer(buf)).toThrow(/No se reconoce el formato|documento_funcionario/);
    });

    it('lanza BadRequest si el archivo está vacío', () => {
      const buf = Buffer.from('', 'utf8');
      expect(() => parser.parseBuffer(buf)).toThrow();
    });

    it('lee correctamente archivos XLSX', () => {
      const buf = xlsx(
        ['documento_funcionario', 'fecha', 'hora_entrada', 'hora_salida'],
        [['1234567', '2026-05-04', '08:00', '17:00']],
      );
      const res = parser.parseBuffer(buf);
      expect(res.errores).toEqual([]);
      expect(res.marcaciones).toHaveLength(2);
    });
  });
});
