const fs = require('fs');
const path = require('path');
const {
  benchmarkDir,
  config,
  readCsv,
  resultsDir,
  writeJson
} = require('./benchmark-lib');

const exists = (name) => fs.existsSync(path.join(resultsDir, name));
const readSummary = (name) => (exists(name) ? readCsv(path.join(resultsDir, name)) : []);

const number = (value) => {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
};

const fmt = (value, suffix = '') => {
  const parsed = number(value);
  return parsed === null ? 'No disponible' : `${parsed.toLocaleString('es-CO', { maximumFractionDigits: 4 })}${suffix}`;
};

const markdownTable = (rows, columns) => {
  if (!rows.length) return 'No hay datos disponibles.\n';
  const header = `| ${columns.map((col) => col.title).join(' |')} |`;
  const sep = `| ${columns.map(() => '---').join(' |')} |`;
  const body = rows.map((row) => `| ${columns.map((col) => row[col.key] ?? '').join(' |')} |`);
  return [header, sep, ...body].join('\n');
};

const criteria = (row) => {
  const group = row.query_group || '';
  const avg = number(row.latency_avg_ms);
  const p95 = number(row.latency_p95_ms);
  const p99 = number(row.latency_p99_ms);
  const cv = number(row.cv_percent);
  if (group === 'busqueda_textual') {
    return avg <= 3000 && p95 <= 5000 && p99 <= 8000 ? 'Cumple latencia textual' : 'No cumple latencia textual';
  }
  return avg <= 1000 && p95 <= 2000 && p99 <= 3000 && cv <= 30
    ? 'Cumple latencia estructurada'
    : 'No cumple latencia estructurada';
};

const main = () => {
  const baseline = readSummary('baseline_summary.csv');
  const optimized = readSummary('optimized_summary.csv');
  const concurrency = readSummary('concurrency_summary.csv');
  const generatedAt = new Date().toISOString();

  const baselineRows = baseline.map((row) => ({
    consulta: row.query_id,
    grupo: row.query_group,
    n: row.n,
    promedio_ms: fmt(row.latency_avg_ms),
    p95_ms: fmt(row.latency_p95_ms),
    p99_ms: fmt(row.latency_p99_ms),
    cv: fmt(row.cv_percent, ' %'),
    fallos: row.failures,
    throughput_s: fmt(row.throughput_per_second),
    criterio: criteria(row)
  }));

  const optimizedRows = optimized.map((row) => ({
    consulta: row.query_id,
    grupo: row.query_group,
    n: row.n,
    promedio_ms: fmt(row.latency_avg_ms),
    p95_ms: fmt(row.latency_p95_ms),
    p99_ms: fmt(row.latency_p99_ms),
    cv: fmt(row.cv_percent, ' %'),
    fallos: row.failures,
    throughput_s: fmt(row.throughput_per_second),
    criterio: criteria(row)
  }));

  const groupSummary = (rows) => {
    const map = new Map();
    for (const row of rows) {
      const key = row.query_group || 'sin_grupo';
      if (!map.has(key)) map.set(key, []);
      map.get(key).push(row);
    }
    return Array.from(map.entries()).map(([group, items]) => {
      const totalN = items.reduce((acc, item) => acc + Number(item.n || 0), 0);
      const weightedAvg =
        totalN > 0
          ? items.reduce((acc, item) => acc + Number(item.latency_avg_ms || 0) * Number(item.n || 0), 0) / totalN
          : null;
      const p95Max = Math.max(...items.map((item) => Number(item.latency_p95_ms || 0)));
      const failures = items.reduce((acc, item) => acc + Number(item.failures || 0), 0);
      return { group, totalN, weightedAvg, p95Max, failures };
    });
  };

  const baselineGroupSummary = groupSummary(baseline);
  const measuredTotal = baseline.reduce((acc, row) => acc + Number(row.n || 0), 0);
  const expectedSequential = config.queries.length * config.measuredRepetitions;
  const baselineArticleText = baselineGroupSummary.length
    ? baselineGroupSummary
        .map(
          (item) =>
            `Para el grupo ${item.group}, la validacion disponible contiene ${item.totalN} operaciones, con latencia promedio de ${fmt(
              item.weightedAvg,
              ' ms'
            )}, p95 de ${fmt(item.p95Max, ' ms')} y ${item.failures} fallos.`
        )
        .join(' ')
    : 'No existen resultados de linea base medidos en los CSV.';

  const report = `# Informe de rendimiento INVIMA

Generado: ${generatedAt}

## 1. Infraestructura

Infraestructura minima de referencia declarada: 4 GB RAM, 2 vCPU compartidas, 80 GB de almacenamiento, 4 TB de transferencia mensual y red nominal de hasta 40 Gbps de entrada / 4 Gbps de salida. Las versiones reales del entorno se registran con \`npm run benchmark:resources\` en \`benchmarks/results/resource_snapshot.json\`.

## 2. Condiciones de prueba

El protocolo completo usa ${config.warmupRepetitions} repeticiones de calentamiento por consulta, ${config.measuredRepetitions} repeticiones medidas por cada una de las siete consultas y ${config.concurrencyRequestsPerLevel} solicitudes por cada nivel de concurrencia (${config.concurrencyLevels.join(', ')} usuarios virtuales). El calentamiento no se incluye en metricas.

## 3. Estado del servidor antes de iniciar

Ver \`benchmarks/results/resource_snapshot.json\`. Si el archivo no existe, ejecute \`npm run benchmark:resources\`.

## 4. Consultas evaluadas

${markdownTable(config.queries.map((query) => ({ id: query.id, nombre: query.name, grupo: query.group, endpoint: query.endpoint })), [
  { key: 'id', title: 'ID' },
  { key: 'nombre', title: 'Consulta' },
  { key: 'grupo', title: 'Grupo' },
  { key: 'endpoint', title: 'Endpoint asociado' }
])}

## 5. Metodologia

Cada ejecucion usa \`EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT JSON)\`, registra latencia total de la operacion SQL, tiempos de planeacion y ejecucion de PostgreSQL, filas estimadas y reales, buffers, estado de exito o fallo, CPU/memoria del proceso de benchmark y conexiones activas a PostgreSQL. Los campos de HTTP quedan como \`SQL_ONLY\` porque estas siete consultas fueron identificadas como mediciones SQL representativas; los endpoints asociados se documentan para trazabilidad.

## 6. Resultados por consulta - linea base

${markdownTable(baselineRows, [
  { key: 'consulta', title: 'Consulta' },
  { key: 'grupo', title: 'Grupo' },
  { key: 'n', title: 'n' },
  { key: 'promedio_ms', title: 'Promedio ms' },
  { key: 'p95_ms', title: 'p95 ms' },
  { key: 'p99_ms', title: 'p99 ms' },
  { key: 'cv', title: 'CV' },
  { key: 'fallos', title: 'Fallos' },
  { key: 'throughput_s', title: 'Throughput/s' },
  { key: 'criterio', title: 'Criterio' }
])}

## 7. Resultados de concurrencia

${markdownTable(concurrency.map((row) => ({
    nivel: row.concurrency_level,
    consulta: row.query_id || 'agregado',
    n: row.n,
    promedio_ms: fmt(row.latency_avg_ms),
    p95_ms: fmt(row.latency_p95_ms),
    fallos: row.failures,
    throughput_s: fmt(row.throughput_per_second)
  })), [
  { key: 'nivel', title: 'Usuarios' },
  { key: 'consulta', title: 'Consulta' },
  { key: 'n', title: 'n' },
  { key: 'promedio_ms', title: 'Promedio ms' },
  { key: 'p95_ms', title: 'p95 ms' },
  { key: 'fallos', title: 'Fallos' },
  { key: 'throughput_s', title: 'Throughput/s' }
])}

## 8. Recursos

Los resultados crudos incluyen CPU del proceso, memoria RSS, operaciones de lectura/escritura reportadas por Node.js y conexiones activas. Para CPU/RAM del servidor y PostgreSQL se debe complementar con herramientas del sistema operativo si se requiere granularidad por proceso.

## 9. EXPLAIN ANALYZE

Cada fila cruda conserva planeacion, ejecucion, filas estimadas/reales y buffers. Para conservar el plan JSON completo, use una corrida puntual con \`BENCHMARK_REPETITIONS=1\` o exporte desde PostgreSQL si se requiere auditoria completa de planes.

## 10. Optimizaciones aplicadas

Las optimizaciones reversibles estan en \`benchmarks/migrations/optimization_up.sql\` y \`benchmarks/migrations/optimization_down.sql\`. Incluyen indices B-tree para orden/filtros frecuentes, indices GIN trigram para los campos usados por \`ILIKE '%marcapasos%'\`, aumento de estadisticas por columna y \`ANALYZE\`.

## 11. Comparacion antes/despues

${optimized.length ? markdownTable(optimizedRows, [
  { key: 'consulta', title: 'Consulta' },
  { key: 'grupo', title: 'Grupo' },
  { key: 'n', title: 'n' },
  { key: 'promedio_ms', title: 'Promedio ms' },
  { key: 'p95_ms', title: 'p95 ms' },
  { key: 'p99_ms', title: 'p99 ms' },
  { key: 'cv', title: 'CV' },
  { key: 'fallos', title: 'Fallos' },
  { key: 'throughput_s', title: 'Throughput/s' },
  { key: 'criterio', title: 'Criterio' }
]) : 'No hay resultados optimizados disponibles. Ejecute `npm run benchmark:optimized` despues de `npm run benchmark:optimize`.\n'}

## 12. Criterios cumplidos y no cumplidos

No se declara cumplimiento sin resultados crudos. Cuando existan CSV completos, esta seccion debe leerse junto con las columnas \`criterio\`, \`failure_rate_percent\`, \`latency_p95_ms\` y \`latency_p99_ms\`.

## 13. Limitaciones

- Las siete consultas representan mediciones SQL; el tiempo HTTP real requiere ejecutar el backend, autenticar y medir endpoints con token.
- La busqueda textual real combina FTS con varios \`ILIKE\`, lo cual puede ser costoso.
- Ejecutar 1 200 operaciones completas puede tardar mucho si las busquedas textuales mantienen tiempos del orden de decenas de segundos.
- EA, MSE, precision y exhaustividad no calculables por ausencia de un conjunto de referencia validado.

## 14. Recomendaciones

- Ejecutar primero una prueba corta con \`BENCHMARK_REPETITIONS=1 BENCHMARK_CONCURRENCY_REQUESTS=7\`.
- Ejecutar el protocolo completo en una ventana controlada.
- Revisar planes de \`query_06\` y \`query_07\` antes y despues de los indices trigram.
- Considerar una refactorizacion futura de busqueda textual para usar una columna \`tsvector\` persistida o una estrategia de union controlada, sin eliminar campos validos.
`;

  fs.writeFileSync(path.join(benchmarkDir, 'INFORME_RENDIMIENTO.md'), report, 'utf8');

  const article = `# Resumen para articulo

El sistema de benchmarking definido para la plataforma INVIMA evalua siete consultas representativas sobre PostgreSQL, con ${config.warmupRepetitions} repeticiones de calentamiento y ${config.measuredRepetitions} repeticiones medidas por consulta, ademas de pruebas concurrentes con ${config.concurrencyLevels.join(', ')} usuarios virtuales. Los resultados crudos se almacenan en CSV y JSON para preservar trazabilidad.

Estado actual: se encontraron ${measuredTotal} operaciones secuenciales medidas en los CSV de linea base. El protocolo completo espera ${expectedSequential} operaciones secuenciales medidas, por lo que cualquier interpretacion debe distinguir la validacion corta de la ejecucion completa. ${baselineArticleText}

Cuando se ejecute el protocolo completo, esta seccion debe reportar numero total de operaciones, latencia promedio por grupo de consulta, percentil 95, throughput, tasa de fallos y comportamiento bajo concurrencia. Si se aplican las optimizaciones reversibles, se debe comparar la linea base contra la medicion optimizada y reportar mejora absoluta y porcentual. No se declara cumplimiento de criterios con una corrida incompleta.

EA, MSE, precision y exhaustividad no calculables por ausencia de un conjunto de referencia validado. El archivo \`benchmarks/ground_truth.example.json\` define la estructura sugerida para incorporar una referencia manual en una etapa posterior.
`;
  fs.writeFileSync(path.join(benchmarkDir, 'RESUMEN_ARTICULO.md'), article, 'utf8');
  writeJson(path.join(resultsDir, 'report_metadata.json'), { generatedAt, baselineRows: baseline.length, optimizedRows: optimized.length, concurrencyRows: concurrency.length });
};

main();
