import type { FilaDocumento } from 'src/catalogo/catalogo.service';
import { diagnosticarDocumento } from 'src/catalogo/diagnostico/diagnosticar-documento';
import { leer } from './extraccion';
import { decidirPlan } from './maquina-estados';
import { pantallaParaCorregir, proyectarDocumento } from './proyeccion';
import { paraEco } from './eco';
import { resolverResultadoTurno } from './resultado';
import { ESTADO_INICIAL, EstadoBot, Extraccion } from './tipos';

const ext = (p: Partial<Extraccion>): Extraccion => ({
  intencion: 'otro',
  tipoDocumento: null,
  referencia: '',
  categoria: 'otro',
  ...p,
});
const estado = (p: Partial<EstadoBot> = {}): EstadoBot => ({ ...ESTADO_INICIAL, ...p });
const doc = {
  codigo: 'c1',
  tipo: 'FACTURA' as const,
  id: '00000000-0000-0000-0000-000000000001',
  nro: '001-001-0000123',
};

describe('decidirPlan', () => {
  it('con número: consulta antes de redactar', () => {
    const p = decidirPlan(
      ext({ intencion: 'incidente_documento', referencia: '1-1-123' }),
      'mi factura 1-1-123 está rechazada',
      estado(),
    );
    expect(p.consultas).toEqual([
      { herramienta: 'q_estado_documento', input: { referencia: '001-001-0000123', tipo: 'CUALQUIERA' } },
    ]);
  });

  it('una pregunta de negocio va al consultor con el texto completo', () => {
    const p = decidirPlan(
      ext({ intencion: 'consulta_negocio', categoria: 'datos_negocio' }),
      '¿cuánto vendí este mes?',
      estado(),
    );
    expect(p.consultas).toEqual([
      { herramienta: 'q_consulta_negocio', input: { pregunta: '¿cuánto vendí este mes?' } },
    ]);
    expect(p.pideReferencia).toBeUndefined();
  });

  it('negocio con un número: es de cobros, no del estado en SIFEN', () => {
    const p = decidirPlan(
      ext({ intencion: 'consulta_negocio', referencia: '123', tipoDocumento: 'FACTURA' }),
      '¿cuánto me pagaron de la factura 123?',
      estado({ documentoActivo: doc }),
    );
    expect(p.consultas[0].herramienta).toBe('q_consulta_negocio');
  });

  it('negocio que nombra un certificado no se desvía a la salud de la empresa', () => {
    const p = decidirPlan(ext({ intencion: 'consulta_negocio' }), 'ventas al cliente Certificados del Sur', estado());
    expect(p.consultas[0].herramienta).toBe('q_consulta_negocio');
  });

  it('una cantidad no es un número de documento', () => {
    const p = decidirPlan(ext({ intencion: 'saludo' }), 'hola, tengo 2 facturas', estado());
    expect(p.consultas).toEqual([]);
  });

  it('sin número: lo pide una vez, la segunda no', () => {
    const primera = decidirPlan(ext({ intencion: 'incidente_documento' }), 'no me salió la factura', estado());
    expect(primera).toMatchObject({ consultas: [], pideReferencia: true });
    const segunda = decidirPlan(
      ext({ intencion: 'incidente_documento' }),
      'no sé el número',
      estado({ pedidosDeReferencia: 1 }),
    );
    expect(segunda).not.toHaveProperty('pideReferencia');
    expect(segunda.consultas.map((c) => c.herramienta)).toEqual(['q_listar_documentos', 'q_alertas_abiertas']);
    expect(segunda.instruccion).toMatch(/No le pidas el número/);
  });

  it('una cantidad no es un número de documento, aunque la intención sea sobre documentos', () => {
    const p = decidirPlan(
      ext({ intencion: 'incidente_documento', referencia: '' }),
      'tengo 3 facturas rechazadas',
      estado(),
    );
    expect(p.consultas).toEqual([]);
    expect(p.pideReferencia).toBe(true);
  });

  it.each(['no me acuerdo del número de la factura', 'no recuerdo el número', 'no tengo el número a mano'])(
    'no sabe el número: "%s"',
    (texto) => {
      const p = decidirPlan(ext({ intencion: 'incidente_documento' }), texto, estado());
      expect(p.consultas[0]?.herramienta).toBe('q_listar_documentos');
    },
  );

  it('si dice que no sabe el número, no se lo pide: lista sus documentos con problemas', () => {
    const p = decidirPlan(
      ext({ intencion: 'incidente_documento' }),
      'no me salió una factura y no sé el número',
      estado(),
    );
    expect(p).not.toHaveProperty('pideReferencia');
    expect(p.consultas[0].herramienta).toBe('q_listar_documentos');
  });

  it('elige un candidato por código, por ordinal o por tipo', () => {
    const candidatos = [
      doc,
      { ...doc, codigo: 'c2', tipo: 'NOTA_CREDITO' as const, id: '00000000-0000-0000-0000-000000000002' },
    ];
    const ref = (p: { consultas: Array<{ input: Record<string, unknown> }> }) => p.consultas[0]?.input.referencia;
    expect(ref(decidirPlan(ext({}), 'es la c2', estado({ candidatos })))).toBe('c2');
    expect(ref(decidirPlan(ext({}), 'la segunda', estado({ candidatos })))).toBe('c2');
    expect(ref(decidirPlan(ext({ tipoDocumento: 'NOTA_CREDITO' }), 'la nota de crédito', estado({ candidatos })))).toBe(
      'c2',
    );
  });

  it('"ya lo reenvié": vuelve a verificar el documento activo', () => {
    const p = decidirPlan(
      ext({ intencion: 'seguimiento' }),
      'listo, ya lo reenvié',
      estado({
        documentoActivo: doc,
        accionPendiente: { accion: 'reenviar', documento: doc, situacionAntes: 'FALLA_SIFEN_TRANSITORIA' },
      }),
    );
    expect(p.consultas[0]).toMatchObject({ herramienta: 'q_estado_documento', input: { referencia: 'c1' } });
    expect(p.instruccion).toMatch(/si ya se ve el cambio/);
  });

  it('certificado, timbrado o alertas: consulta la empresa, no pide un número', () => {
    const cert = decidirPlan(
      ext({ intencion: 'consulta_dato', categoria: 'timbrado_certificado' }),
      '¿mi certificado venció?',
      estado(),
    );
    expect(cert.consultas).toEqual([{ herramienta: 'q_salud_empresa', input: {} }]);
    const alertas = decidirPlan(ext({ intencion: 'consulta_dato' }), '¿tengo alguna alerta pendiente?', estado());
    expect(alertas.consultas).toEqual([{ herramienta: 'q_alertas_abiertas', input: {} }]);
    const deUso = decidirPlan(
      ext({ intencion: 'consulta_operativa' }),
      '¿cómo cargo el certificado digital?',
      estado(),
    );
    expect(deUso.consultas[0].herramienta).toBe('q_buscar_guia');
  });

  it('un dato que no es de un documento no pide número: va a las guías', () => {
    const p = decidirPlan(
      ext({ intencion: 'consulta_dato', categoria: 'otro' }),
      '¿cuánto cuesta el plan Pro?',
      estado(),
    );
    expect(p.pideReferencia).toBeUndefined();
    expect(p.consultas[0].herramienta).toBe('q_buscar_guia');
  });

  it('pregunta de uso: solo guías', () => {
    const p = decidirPlan(ext({ intencion: 'consulta_operativa' }), '¿cómo anulo una factura?', estado());
    expect(p.consultas).toEqual([{ herramienta: 'q_buscar_guia', input: { pregunta: '¿cómo anulo una factura?' } }]);
  });
});

describe('resolverResultadoTurno', () => {
  const base = { cierre: null, veredicto: null, huboConsultaRealAntes: false, rechazoDelModelo: false };

  it('manda el diagnóstico del documento', () => {
    const r = resolverResultadoTurno({
      ...base,
      cierre: { resultado: 'resuelto', categoria: 'documento_sifen', motivo: null },
      veredicto: { situacion: 'TRABADO', resultado: 'NO_RESUELTO', motivo: 'trabado_sifen' },
    });
    expect(r).toMatchObject({ resultado: 'NO_RESUELTO', motivo: 'trabado_sifen', esConsultaReal: true });
  });

  it('un no_resuelto del modelo gana sobre un diagnóstico resuelto (lectura conservadora)', () => {
    const r = resolverResultadoTurno({
      ...base,
      cierre: { resultado: 'no_resuelto', categoria: 'envio_correo', motivo: 'posible_error_sistema' },
      veredicto: { situacion: 'APROBADO', resultado: 'RESUELTO' },
    });
    expect(r.resultado).toBe('NO_RESUELTO');
  });

  it('un saludo sin consulta previa no es una consulta', () => {
    const r = resolverResultadoTurno({ ...base, cierre: { resultado: 'saludo', categoria: 'otro', motivo: null } });
    expect(r).toMatchObject({ resultado: 'SIN_CONSULTA', esConsultaReal: false });
  });

  it('pedir un dato deja el ticket abierto sin resultado', () => {
    const r = resolverResultadoTurno({
      ...base,
      cierre: { resultado: 'incompleto', categoria: 'documento_sifen', motivo: null },
    });
    expect(r.resultado).toBeNull();
  });

  it('un rechazo del modelo es no resuelto', () => {
    expect(resolverResultadoTurno({ ...base, rechazoDelModelo: true }).resultado).toBe('NO_RESUELTO');
  });
});

describe('proyectarDocumento', () => {
  const fila = {
    tipo: 'FACTURA',
    id: '11111111-2222-3333-4444-555555555555',
    establecimiento: '001',
    punto_expedicion: '001',
    numero: '0000123',
    nro_documento: '001-001-0000123',
    fecha_emision: new Date('2026-10-20T10:00:00Z'),
    estado: 'Rechazado',
    estado_sifen: 'Rechazado',
    mensaje_sifen: 'TEST - RUC del receptor inexistente en la base de datos de Marangatu',
    tipo_aprobacion: null,
    cdc: '01800000000000000000000000000000000000000000',
    nro_lote: '5',
    fecha_envio_sifen: new Date('2026-10-20T10:01:00Z'),
    fecha_firma_sifen: null,
    fecha_registro_sifen: null,
    estado_envio_email: null,
    evento_aplicado: null,
    estado_evento: null,
    mensaje_evento: null,
    fecha_evento: null,
    total: '150000',
    created_at: new Date('2026-10-20T10:00:00Z'),
    updated_at: new Date('2026-10-20T10:01:00Z'),
  } as FilaDocumento;

  it('no expone ids internos y lleva a la pantalla donde se corrige', () => {
    const v = diagnosticarDocumento(fila, {
      ahora: new Date('2026-10-21T00:00:00Z'),
      horasParaTrabado: 2,
      sifenActivo: true,
    });
    const p = proyectarDocumento('c1', fila, v, []);
    expect(JSON.stringify(p)).not.toContain(fila.id);
    expect(p.diagnostico).toMatchObject({
      resultado: 'RESUELTO_AUTOSERVICIO',
      mensaje_sifen: 'RUC del receptor inexistente en la base de datos de Marangatu',
      donde: { miga: 'Menú principal › Contactos', ruta: '/clientes' },
    });
    expect(p.documento.ambiente_de_pruebas).toBe(true);
  });

  it('pantalla según el dato que rechazó SIFEN', () => {
    expect(
      pantallaParaCorregir('XML malformado: [El valor HB-F7206 del elemento: dGtin es invalido]', 'FACTURA').ruta,
    ).toBe('/configuracion/productos');
    expect(pantallaParaCorregir('Código de actividad económica incorrecto', 'FACTURA').ruta).toBe(
      '/configuracion/empresa',
    );
    expect(pantallaParaCorregir('Proporción gravada del IVA incorrecta', 'NOTA_CREDITO').ruta).toBe(
      '/ventas/notas-credito',
    );
  });
});

describe('paraEco (después de un fallback)', () => {
  it('saca razonamiento y tool_use anteriores al último fallback; el texto queda', () => {
    const contenido = [
      { type: 'thinking', thinking: '', signature: 'x' },
      { type: 'text', text: 'Parcial' },
      { type: 'tool_use', id: 't1', name: 'q_salud_empresa', input: {} },
      { type: 'fallback', from: { model: 'a' }, to: { model: 'b' } },
      { type: 'tool_use', id: 't2', name: 'q_salud_empresa', input: {} },
    ] as never[];
    expect(paraEco(contenido).map((b: { type: string }) => b.type)).toEqual(['text', 'tool_use']);
  });

  it('sin fallback no toca nada', () => {
    const contenido = [{ type: 'text', text: 'hola' }] as never[];
    expect(paraEco(contenido)).toBe(contenido);
  });
});

describe('extractor: lectura defensiva', () => {
  it('valores fuera del esquema caen a neutros', () => {
    expect(leer({ intencion: 'hackear', tipo_documento: 'BOLETA', referencia: 5, categoria: 'x' })).toEqual({
      intencion: 'otro',
      tipoDocumento: null,
      referencia: '',
      categoria: 'otro',
    });
    expect(
      leer({
        intencion: 'consulta_dato',
        tipo_documento: 'NINGUNO',
        referencia: ' 123 ',
        categoria: 'documento_sifen',
      }),
    ).toEqual({
      intencion: 'consulta_dato',
      tipoDocumento: null,
      referencia: '123',
      categoria: 'documento_sifen',
    });
  });
});
