/**
 * Invalida TODAS las entradas en `metricas_mensuales` — marca para recompute
 * cada (contribuyente_id, anio, mes) que tenga datos cacheados. Diseñado para
 * usarse después de un cambio de fórmula que afecta resultados históricos
 * (ej. 2026-04-23: NC tipo E con TipoRelacion=07 dejan de restar en Grupo 1).
 *
 * El cron `metricas-invalidations.job` (cada 15min) procesa el backlog.
 * Para acelerar: `pnpm --filter @horux/api exec tsx -e "import { runProcessInvalidations } from './src/jobs/metricas-invalidations.job.js'; runProcessInvalidations().then(()=>process.exit(0))"`
 *
 * Uso:
 *   pnpm --filter @horux/api exec tsx scripts/invalidate-metricas-all.ts         # ejecuta
 *   pnpm --filter @horux/api exec tsx scripts/invalidate-metricas-all.ts --dry   # reporta sin escribir
 */
import { prisma, tenantDb } from '../src/config/database.js';

const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run');
const REASON = process.argv.find(a => a.startsWith('--reason='))?.slice(9) || 'FORMULA_CHANGE_E07_GRUPO1';

interface PerTenantResult {
  tenantId: string;
  rfc: string;
  databaseName: string;
  metricasRows: number;
  marcadasNuevas: number;
  marcadasUpdate: number;
  error?: string;
}

async function invalidateTenant(
  tenantId: string,
  rfc: string,
  databaseName: string,
): Promise<PerTenantResult> {
  const result: PerTenantResult = {
    tenantId,
    rfc,
    databaseName,
    metricasRows: 0,
    marcadasNuevas: 0,
    marcadasUpdate: 0,
  };

  const pool = await tenantDb.getPool(tenantId, databaseName);

  // Cuenta filas existentes en metricas_mensuales para reportar
  const { rows: cnt } = await pool.query<{ n: number }>(
    `SELECT COUNT(DISTINCT (contribuyente_id, anio, mes))::int AS n FROM metricas_mensuales`,
  );
  result.metricasRows = cnt[0]?.n || 0;
  if (result.metricasRows === 0) return result;

  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    // Insert-or-update: si ya estaba marcada, sobrescribe reason y marcado_at
    // para que el cron la re-procese con el motivo correcto.
    const { rows: inserted } = await client.query<{
      contribuyente_id: string;
      anio: number;
      mes: number;
      was_new: boolean;
    }>(
      `
      INSERT INTO metricas_invalidaciones (contribuyente_id, anio, mes, reason)
      SELECT DISTINCT contribuyente_id, anio, mes, $1 AS reason
      FROM metricas_mensuales
      ON CONFLICT (contribuyente_id, anio, mes) DO UPDATE
        SET reason = EXCLUDED.reason, marcado_at = now()
      RETURNING contribuyente_id, anio, mes, (xmax = 0) AS was_new
      `,
      [REASON],
    );

    result.marcadasNuevas = inserted.filter(r => r.was_new).length;
    result.marcadasUpdate = inserted.length - result.marcadasNuevas;

    if (DRY_RUN) {
      await client.query('ROLLBACK');
    } else {
      await client.query('COMMIT');
    }
  } catch (err: any) {
    await client.query('ROLLBACK').catch(() => {});
    result.error = err?.message || String(err);
  } finally {
    client.release();
  }

  return result;
}

async function main() {
  console.log(`=== Invalidate metricas_mensuales ${DRY_RUN ? '(DRY RUN — no writes)' : ''} ===`);
  console.log(`Reason: ${REASON}\n`);

  const tenants = await prisma.tenant.findMany({
    where: { active: true },
    select: { id: true, rfc: true, databaseName: true },
    orderBy: { rfc: 'asc' },
  });

  console.log(`Tenants activos: ${tenants.length}\n`);

  const results: PerTenantResult[] = [];
  for (const t of tenants) {
    process.stdout.write(`[${t.rfc}] (${t.databaseName}) ... `);
    try {
      const r = await invalidateTenant(t.id, t.rfc, t.databaseName);
      results.push(r);
      if (r.error) {
        console.log(`ERROR: ${r.error}`);
      } else if (r.metricasRows === 0) {
        console.log(`sin cache (skip)`);
      } else {
        console.log(
          `cache=${r.metricasRows} (contrib,año,mes), marcadas=${r.marcadasNuevas + r.marcadasUpdate} (nuevas=${r.marcadasNuevas}, re-marcadas=${r.marcadasUpdate})`,
        );
      }
    } catch (err: any) {
      console.log(`FATAL: ${err?.message || err}`);
      results.push({
        tenantId: t.id,
        rfc: t.rfc,
        databaseName: t.databaseName,
        metricasRows: 0,
        marcadasNuevas: 0,
        marcadasUpdate: 0,
        error: err?.message || String(err),
      });
    }
  }

  const totalMetricas = results.reduce((s, r) => s + r.metricasRows, 0);
  const totalMarcadas = results.reduce((s, r) => s + r.marcadasNuevas + r.marcadasUpdate, 0);
  const tenantsTouched = results.filter(r => r.marcadasNuevas + r.marcadasUpdate > 0).length;
  const tenantsFailed = results.filter(r => r.error).length;

  console.log(`\n=== Resumen ===`);
  console.log(`  Tenants procesados:  ${results.length}`);
  console.log(`  Tenants con cache:   ${tenantsTouched}`);
  console.log(`  Filas cache total:   ${totalMetricas}`);
  console.log(`  Invalidaciones:      ${totalMarcadas}${DRY_RUN ? ' (rolled back)' : ''}`);
  if (tenantsFailed > 0) console.log(`  Tenants con error:   ${tenantsFailed}`);

  if (!DRY_RUN && totalMarcadas > 0) {
    console.log(`\nCron metricas-invalidations procesará el backlog en <=15 min.`);
    console.log(`Para disparar manual: runProcessInvalidations() desde un tsx -e ad-hoc.`);
  }

  await prisma.$disconnect();
  process.exit(tenantsFailed > 0 ? 1 : 0);
}

main().catch(async (err) => {
  console.error('Fatal:', err);
  await prisma.$disconnect().catch(() => {});
  process.exit(1);
});
