/**
 * Golden set (plan §9.2): compuerta antes de cambiar un prompt, un modelo o el effort.
 *
 *   pnpm build && pnpm golden            (sale con código 1 si algo cambió)
 *
 * Toma las respuestas del agente etiquetadas como CORRECTAS en el reporte y, para
 * cada turno, vuelve a correr con el código actual:
 *   1. el plan: extractor (o el atajo sin modelo) + máquina de estados → ¿consultaría
 *      lo mismo que se consultó? Se compara contra la primera traza del turno.
 *   2. el diagnóstico: diagnosticarDocumento sobre el documento guardado en la traza,
 *      con la fecha de ese momento → ¿misma situación y mismo resultado?
 * La redacción no se compara automático: se revisa a mano en el reporte.
 *
 * Usa la base propia (SOPORTE_DATABASE_URL) y, para el extractor, la API de Anthropic.
 */
import 'dotenv/config';
import { PrismaClient } from '@prisma/client';
import { diagnosticarDocumento } from 'src/catalogo/diagnostico/diagnosticar-documento';
import { envs } from 'src/config/envs';
import { AnthropicService } from 'src/llm/anthropic.service';
import { extraccionDirecta } from 'src/pipeline/extraccion';
import { ExtractorService } from 'src/pipeline/extractor.service';
import { decidirPlan } from 'src/pipeline/maquina-estados';
import { ESTADO_INICIAL } from 'src/pipeline/tipos';

interface Diferencia {
  caso: string;
  que: string;
  antes: unknown;
  ahora: unknown;
}

async function main() {
  const prisma = new PrismaClient();
  const extractor = new ExtractorService(new AnthropicService());
  const diferencias: Diferencia[] = [];
  let casos = 0;
  let planesIguales = 0;
  let planesOmitidos = 0;
  let diagnosticos = 0;
  let diagnosticosIguales = 0;

  const correctas = await prisma.mensajes.findMany({
    where: { autor: 'bot', correcta: true },
    orderBy: { created_at: 'asc' },
    include: { ticket: { select: { id: true, numero: true } } },
  });

  for (const respuesta of correctas) {
    const caso = `SOP-${respuesta.ticket.numero}#${respuesta.orden}`;
    const pregunta = await prisma.mensajes.findFirst({
      where: { ticket_id: respuesta.ticket_id, autor: 'cliente', orden: { lt: respuesta.orden } },
      orderBy: { orden: 'desc' },
    });
    const trazas = await prisma.trazas.findMany({
      where: { ticket_id: respuesta.ticket_id, mensaje_orden: respuesta.orden },
      orderBy: { created_at: 'asc' },
    });
    if (!pregunta) continue;
    casos += 1;

    // 1. Plan. Los turnos que dependen del estado (eligió "c2", "ya lo corregí") no se
    //    pueden reproducir sin el estado de ese momento: se informan como omitidos.
    const original = trazas.find((t) => t.herramienta !== 'deteccion_otro_ruc');
    const referenciaOriginal = (original?.parametros as Record<string, unknown> | null)?.referencia;
    if (typeof referenciaOriginal === 'string' && /^c\d+$/i.test(referenciaOriginal)) {
      planesOmitidos += 1;
    } else {
      const directa = extraccionDirecta(pregunta.texto);
      const extraccion = directa ?? (await extractor.extraer([], pregunta.texto)).extraccion;
      const plan = decidirPlan(extraccion, pregunta.texto, { ...ESTADO_INICIAL });
      const ahora = plan.consultas[0]
        ? { herramienta: plan.consultas[0].herramienta, ...plan.consultas[0].input }
        : null;
      const antes = original ? { herramienta: original.herramienta, ...(original.parametros as object) } : null;
      if (mismoPlan(antes, ahora)) planesIguales += 1;
      else diferencias.push({ caso, que: 'plan', antes, ahora });
    }

    // 2. Diagnóstico, con la fecha del momento en que se hizo.
    for (const t of trazas.filter((x) => x.herramienta === 'q_estado_documento')) {
      const r = t.respuesta as {
        ok?: boolean;
        documento?: Record<string, unknown>;
        veredicto?: Record<string, unknown>;
      };
      if (!r?.ok || !r.documento || !r.veredicto) continue;
      diagnosticos += 1;
      const documento = revivirFechas(r.documento);
      const nuevo = diagnosticarDocumento(documento as never, {
        ahora: t.created_at,
        horasParaTrabado: envs.horasParaTrabado,
        sifenActivo: null,
      });
      const antes = { situacion: r.veredicto.situacion, resultado: r.veredicto.resultado };
      const ahora = { situacion: nuevo.situacion, resultado: nuevo.resultado };
      if (antes.situacion === ahora.situacion && antes.resultado === ahora.resultado) diagnosticosIguales += 1;
      else diferencias.push({ caso, que: 'diagnóstico', antes, ahora });
    }
  }

  console.log(`Golden set: ${casos} turnos etiquetados como correctos`);
  console.log(
    `  plan:        ${planesIguales}/${casos - planesOmitidos} iguales (${planesOmitidos} dependen del estado y se omiten)`,
  );
  console.log(`  diagnóstico: ${diagnosticosIguales}/${diagnosticos} iguales`);
  for (const d of diferencias) {
    console.log(
      `  ✗ ${d.caso} · ${d.que}\n      antes: ${JSON.stringify(d.antes)}\n      ahora: ${JSON.stringify(d.ahora)}`,
    );
  }
  if (casos === 0) console.log('  (no hay respuestas etiquetadas: etiquetalas desde el reporte)');
  await prisma.$disconnect();
  process.exitCode = diferencias.length > 0 ? 1 : 0;
}

/**
 * Misma herramienta y, para un documento, misma referencia y tipo. En listados
 * solo cuenta que se liste: días y estados son política, no comprensión.
 */
function mismoPlan(a: Record<string, unknown> | null, b: Record<string, unknown> | null): boolean {
  if (!a || !b) return a === b;
  if (a.herramienta === 'q_listar_documentos' || b.herramienta === 'q_listar_documentos') {
    return a.herramienta === b.herramienta;
  }
  const clave = (x: Record<string, unknown>) =>
    JSON.stringify([x.herramienta, x.referencia ?? null, x.tipo ?? null, x.pregunta ? 'guia' : null]);
  return clave(a) === clave(b);
}

/** Las fechas vuelven de jsonb como texto; el diagnóstico las necesita como Date. */
function revivirFechas(doc: Record<string, unknown>): Record<string, unknown> {
  const salida: Record<string, unknown> = { ...doc };
  for (const [k, v] of Object.entries(doc)) {
    if (typeof v === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(v)) salida[k] = new Date(v);
  }
  return salida;
}

main().catch((err) => {
  console.error(err);
  process.exitCode = 1;
});
