import { readFileSync, readdirSync, statSync } from 'fs';
import { join } from 'path';

/**
 * Orden de las migraciones del CRM.
 *
 * Prisma aplica las migraciones en **orden alfabético del nombre del directorio**,
 * no por dependencia. Si una migración referencia una tabla que crea otra que
 * ordena después, `migrate deploy` falla a mitad de camino y deja el tracker en
 * estado fallido, que después hay que resolver a mano en cada entorno.
 *
 * Pasó el 08/10/2026 desplegando a test: `20261002_crm_actividades` ordenaba antes
 * que `20261002_crm_oportunidades` y murió con `relation "crm_oportunidades" does
 * not exist`. El dry-run no lo había detectado porque encadenaba los archivos en
 * orden lógico y no en el orden real de Prisma.
 *
 * Esto compara, para cada tabla `crm_` referenciada, en qué migración se crea.
 */

const DIR = join(__dirname, '..', '..', 'prisma', 'migrations');

function migracionesCrm(): string[] {
  return readdirSync(DIR)
    .filter((n) => n.includes('crm'))
    .filter((n) => statSync(join(DIR, n)).isDirectory())
    .sort(); // el mismo orden que usa Prisma
}

function sql(nombre: string): string {
  return readFileSync(join(DIR, nombre, 'migration.sql'), 'utf8');
}

/** Quita los comentarios: un `-- REFERENCES crm_x` no es una dependencia real. */
function sinComentarios(texto: string): string {
  return texto
    .split('\n')
    .filter((l) => !l.trim().startsWith('--'))
    .join('\n');
}

function creadas(texto: string): string[] {
  return [...sinComentarios(texto).matchAll(/CREATE TABLE (?:IF NOT EXISTS )?(crm_\w+)/gi)].map((m) => m[1]);
}

function referenciadas(texto: string): string[] {
  const cuerpo = sinComentarios(texto);
  return [
    ...[...cuerpo.matchAll(/REFERENCES (crm_\w+)/gi)].map((m) => m[1]),
    ...[...cuerpo.matchAll(/ALTER TABLE (crm_\w+)/gi)].map((m) => m[1]),
  ];
}

describe('migraciones del CRM — orden de aplicación', () => {
  const migraciones = migracionesCrm();

  it('hay migraciones del CRM que revisar', () => {
    expect(migraciones.length).toBeGreaterThan(5);
  });

  /**
   * El test de verdad: ninguna migración puede tocar una tabla `crm_` que se cree
   * en otra posterior. Una tabla que se crea en la misma migración vale.
   */
  it('ninguna referencia una tabla crm_ que se cree después', () => {
    const creadaEn = new Map<string, number>();
    migraciones.forEach((nombre, i) => {
      for (const tabla of creadas(sql(nombre))) {
        if (!creadaEn.has(tabla)) creadaEn.set(tabla, i);
      }
    });

    const problemas: string[] = [];
    migraciones.forEach((nombre, i) => {
      const propias = new Set(creadas(sql(nombre)));
      for (const tabla of new Set(referenciadas(sql(nombre)))) {
        if (propias.has(tabla)) continue;
        const origen = creadaEn.get(tabla);
        if (origen === undefined) {
          // La tabla no la crea ninguna migración del CRM: o es de otro módulo o
          // falta. Las de Fase 0 están en 20261001_crm_base, así que esto no
          // debería pasar; si pasa, hay que mirarlo.
          problemas.push(`${nombre} referencia ${tabla}, que ninguna migración del CRM crea`);
          continue;
        }
        if (origen > i) {
          problemas.push(`${nombre} referencia ${tabla}, creada después en ${migraciones[origen]}`);
        }
      }
    });

    expect(problemas).toEqual([]);
  });

  it('actividades queda después de oportunidades', () => {
    const i = (parte: string) => migraciones.findIndex((n) => n.includes(parte));
    expect(i('actividades')).toBeGreaterThan(i('oportunidades'));
  });

  it('oportunidades queda después de leads', () => {
    const i = (parte: string) => migraciones.findIndex((n) => n.includes(parte));
    expect(i('oportunidades')).toBeGreaterThan(i('leads'));
  });

  /** `uuid_generate_v4()` y no `gen_random_uuid()`: es la convención del repo. */
  it('todas usan uuid_generate_v4', () => {
    const malas = migraciones.filter((n) => sinComentarios(sql(n)).includes('gen_random_uuid'));
    expect(malas).toEqual([]);
  });
});
