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

const outFile = path.join(resultsDir, 'validation_search_results.json');
const mode = process.argv[2] || process.env.BENCHMARK_VALIDATION_PHASE || 'after';

const whereSql = `
WHERE (
  to_tsvector('spanish', COALESCE(search_text, '')) @@ websearch_to_tsquery('spanish', 'marcapasos')
  OR COALESCE(registro_sanitario, '') ILIKE '%marcapasos%'
  OR COALESCE(expediente, '') ILIKE '%marcapasos%'
  OR COALESCE(producto, '') ILIKE '%marcapasos%'
  OR COALESCE(categoria_canonica, '') ILIKE '%marcapasos%'
  OR COALESCE(categoria_linea, '') ILIKE '%marcapasos%'
)`;

const countSql = `SELECT COUNT(*)::bigint AS total FROM invima_api_catalog ${whereSql};`;

const pageSql = `
WITH matched AS MATERIALIZED (
  SELECT
    source_dataset_key,
    source_uid,
    source_dataset_id,
    source_dataset_name,
    categoria_linea,
    categoria_canonica,
    categoria_slug,
    expediente,
    registro_sanitario,
    producto,
    titular,
    estado_registro,
    fecha_expedicion,
    fecha_vencimiento,
    modalidad,
    grupo,
    marca,
    principio_activo,
    forma_farmaceutica,
    presentacion_comercial,
    atc,
    rol_nombre,
    rol_tipo,
    ciudad_titular,
    pais_titular,
    fabricante,
    importador,
    vigencia
  FROM invima_api_catalog
  ${whereSql}
)
SELECT
  source_dataset_key,
  source_uid,
  source_dataset_id,
  source_dataset_name,
  categoria_linea,
  categoria_canonica,
  categoria_slug,
  expediente,
  registro_sanitario,
  producto,
  titular,
  estado_registro,
  fecha_expedicion,
  fecha_vencimiento,
  modalidad,
  grupo,
  marca,
  principio_activo,
  forma_farmaceutica,
  presentacion_comercial,
  atc,
  rol_nombre,
  rol_tipo,
  ciudad_titular,
  pais_titular,
  fabricante,
  importador,
  vigencia
FROM matched
ORDER BY fecha_vencimiento DESC NULLS LAST, producto ASC NULLS LAST, source_dataset_key ASC, source_uid ASC
LIMIT 25 OFFSET 0;
`;

const rowId = (row) => `${row.source_dataset_key}|${row.source_uid}`;
const normalizeDate = (value) => {
  if (!value) return '';
  if (value instanceof Date) return value.toISOString();
  return String(value);
};
const orderKey = (row) =>
  `${normalizeDate(row.fecha_vencimiento)}|${row.producto || ''}|${row.source_dataset_key}|${row.source_uid}`;

const duplicates = (ids) => {
  const seen = new Set();
  const dupes = new Set();
  for (const id of ids) {
    if (seen.has(id)) dupes.add(id);
    seen.add(id);
  }
  return Array.from(dupes);
};

const diff = (before, after) => {
  const beforeIds = before.rows.map(rowId);
  const afterIds = after.rows.map(rowId);
  const beforeOrderKeys = before.rows.map(orderKey);
  const afterOrderKeys = after.rows.map(orderKey);
  const beforeSet = new Set(beforeIds);
  const afterSet = new Set(afterIds);
  const missing = beforeIds.filter((id) => !afterSet.has(id));
  const additional = afterIds.filter((id) => !beforeSet.has(id));
  const orderChanged =
    beforeIds.length !== afterIds.length || beforeIds.some((id, index) => id !== afterIds[index]);
  const orderKeysChanged =
    beforeOrderKeys.length !== afterOrderKeys.length ||
    beforeOrderKeys.some((key, index) => key !== afterOrderKeys[index]);
  const countBefore = Number(before.count);
  const countAfter = Number(after.count);
  const validResultSet =
    countBefore === countAfter &&
    before.rows.length === after.rows.length &&
    missing.length === 0 &&
    additional.length === 0 &&
    duplicates(afterIds).length === 0;
  const validStrictOrder = validResultSet && !orderChanged;
  const validOrderKeys = validResultSet && !orderKeysChanged;

  return {
    countBefore,
    countAfter,
    countMatches: countBefore === countAfter,
    rowCountBefore: before.rows.length,
    rowCountAfter: after.rows.length,
    rowCountMatches: before.rows.length === after.rows.length,
    missingIds: missing,
    additionalIds: additional,
    duplicateIdsBefore: duplicates(beforeIds),
    duplicateIdsAfter: duplicates(afterIds),
    orderChanged,
    orderKeysChanged,
    idOrderChangedWithinTies: orderChanged && !orderKeysChanged,
    validResultSet,
    validStrictOrder,
    validOrderKeys,
    valid: validResultSet && validOrderKeys,
    note:
      orderChanged && !orderKeysChanged
        ? 'Los mismos registros aparecen bajo las mismas claves ORDER BY; cambia el orden interno de empates porque la consulta no incluye un desempate unico.'
        : ''
  };
};

const capture = async () => {
  const client = await createClient();
  try {
    await client.query('SET statement_timeout = 0');
    const count = await client.query(countSql);
    const page = await client.query(pageSql);
    return {
      capturedAt: new Date().toISOString(),
      count: Number(count.rows[0]?.total || 0),
      rows: page.rows,
      ids: page.rows.map(rowId)
    };
  } finally {
    await client.end();
  }
};

const main = async () => {
  const current = fs.existsSync(outFile)
    ? JSON.parse(fs.readFileSync(outFile, 'utf8'))
    : {
        query: 'marcapasos',
        validationSql: {
          count: countSql.trim(),
          page: pageSql.trim()
        }
      };

  if (mode === 'before') {
    current.before = await capture();
    delete current.after;
    delete current.comparison;
  } else {
    if (!current.before) {
      throw new Error('No existe referencia before. Ejecute: node benchmarks/scripts/validate-search-results.js before');
    }
    current.after = await capture();
    current.comparison = diff(current.before, current.after);
  }

  writeJson(outFile, current);
  console.log(JSON.stringify(current.comparison || { beforeCaptured: Boolean(current.before) }, null, 2));
};

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