import { MigrationInterface, QueryRunner, TableColumn, TableIndex } from "typeorm";

// RECONCILIACION, no edicion de una migracion aplicada: CreateTallerTables1738852000000
// creo taller_ot_cosas_trae SIN cantidad, SIN observacion, y SIN el UNIQUE
// (ot_id, cosa_trae_id) que la entidad (OtCosaTrae, @Unique(["otId","cosaTraeId"]))
// y la demo real (mbinvautocentrorodriguez) SI tienen. La migracion original
// nunca se habia corrido de punta a punta contra una base genuinamente nueva —
// el esquema de la demo salio de otro lado (verificado, no supuesto).
//
// Es LA MAS URGENTE de las reconciliaciones: sin el UNIQUE, un tenant nuevo
// puede acumular la misma (ot_id, cosa_trae_id) repetida sin que nada lo
// impida — corrupcion lenta y silenciosa, no un error visible como las
// columnas faltantes.
//
// Defensiva: no-op donde ya esta bien (la demo, verificado con
// getTable()+findColumnByName/indices — las UNIQUE KEY de MySQL viven en
// table.indices con isUnique=true, NUNCA en table.uniques, confirmado
// empiricamente antes de escribir esto). Aborta con un mensaje claro si
// encuentra datos que el UNIQUE no podria aceptar, en vez de dejar que
// MySQL falle con un error de clave duplicada poco claro.
export class ReconcileOtCosasTrae1770000000000 implements MigrationInterface {
  public async up(queryRunner: QueryRunner): Promise<void> {
    const table = await queryRunner.getTable("taller_ot_cosas_trae");
    if (!table) return; // Nunca deberia pasar (la crea CreateTallerTables), pero no asumir.

    if (!table.findColumnByName("cantidad")) {
      await queryRunner.addColumn(
        "taller_ot_cosas_trae",
        new TableColumn({ name: "cantidad", type: "int", default: 1 }),
      );
    }

    if (!table.findColumnByName("observacion")) {
      await queryRunner.addColumn(
        "taller_ot_cosas_trae",
        new TableColumn({
          name: "observacion",
          type: "varchar",
          length: "255",
          isNullable: true,
        }),
      );
    }

    const refreshed = await queryRunner.getTable("taller_ot_cosas_trae");
    const hasUnique = refreshed?.indices.some(
      (i) =>
        i.isUnique &&
        i.columnNames.length === 2 &&
        i.columnNames.includes("ot_id") &&
        i.columnNames.includes("cosa_trae_id"),
    );

    if (!hasUnique) {
      const duplicates = await queryRunner.query(
        `SELECT ot_id, cosa_trae_id, COUNT(*) as c
         FROM taller_ot_cosas_trae
         GROUP BY ot_id, cosa_trae_id
         HAVING c > 1
         LIMIT 1`,
      );
      if (duplicates.length > 0) {
        throw new Error(
          "No se puede agregar el UNIQUE (ot_id, cosa_trae_id) en taller_ot_cosas_trae: " +
            `ya existen filas duplicadas (ej. ot_id=${duplicates[0].ot_id}, ` +
            `cosa_trae_id=${duplicates[0].cosa_trae_id}, repetida ${duplicates[0].c} veces). ` +
            "Resolver la duplicacion a mano antes de reintentar esta migracion — " +
            "no hay forma automatica y segura de decidir cual fila conservar.",
        );
      }

      // MySQL no tiene un objeto "unique constraint" separado de un indice
      // unico (verificado: createUniqueConstraint() tira
      // "MySql does not support unique constraints. Use unique index instead.") —
      // por eso createIndex() con isUnique:true, no TableUnique.
      await queryRunner.createIndex(
        "taller_ot_cosas_trae",
        new TableIndex({
          name: "uk_ot_cosa",
          columnNames: ["ot_id", "cosa_trae_id"],
          isUnique: true,
        }),
      );
    }

    // Residuo de un down() anterior: para poder borrar uk_ot_cosa sin romper
    // el FK de ot_id, down() crea antes un indice simple de soporte
    // (IDX_ot_cosas_trae_ot_id, ver mas abajo). Una vez que uk_ot_cosa existe
    // (recien creado arriba, o ya estaba), ese indice de soporte es
    // redundante — su columna lider (ot_id) ya queda cubierta por uk_ot_cosa.
    // Sin este chequeo, un up() corrido despues de un down() deja un esquema
    // DISTINTO al de un alta limpia (el indice de soporte de mas) — bug real,
    // encontrado corriendo de verdad el ciclo down()+up(), no razonando sobre
    // el codigo. Se borra aca para que un reintento (revert+reapply, que es
    // lo que hara el futuro runner del panel si una migracion queda a medio
    // aplicar) termine exactamente igual que una base nueva.
    const final = await queryRunner.getTable("taller_ot_cosas_trae");
    if (final?.indices.some((i) => i.name === "IDX_ot_cosas_trae_ot_id")) {
      await queryRunner.dropIndex("taller_ot_cosas_trae", "IDX_ot_cosas_trae_ot_id");
    }
  }

  // Revertir de verdad significaria DROPear cantidad/observacion — real
  // perdida de datos si algun tenant ya las uso. Se verifica ANTES de
  // tocar nada: si toda fila sigue en el valor por defecto (cantidad=1,
  // observacion=NULL — es decir, nadie las uso desde que up() corrio),
  // revertir es seguro. Si alguna fila se desvio del default, ABORTA con
  // un mensaje claro en vez de borrar datos en silencio — un rollback
  // silencioso que pierde datos es peor que no tener rollback.
  public async down(queryRunner: QueryRunner): Promise<void> {
    const table = await queryRunner.getTable("taller_ot_cosas_trae");
    if (!table) return;

    const usadas = await queryRunner.query(
      `SELECT COUNT(*) as c FROM taller_ot_cosas_trae
       WHERE cantidad != 1 OR observacion IS NOT NULL`,
    );
    if (Number(usadas[0].c) > 0) {
      throw new Error(
        `No se puede revertir ReconcileOtCosasTrae1770000000000: ${usadas[0].c} fila(s) ` +
          "tienen cantidad!=1 u observacion!=NULL — revertir borraria esos datos reales. " +
          "ABORTA. Si de verdad hace falta revertir, decidir a mano que hacer con esos datos primero.",
      );
    }

    const hasUnique = table.indices.some((i) => i.name === "uk_ot_cosa");
    if (hasUnique) {
      // MySQL no deja borrar uk_ot_cosa directamente: el FK de ot_id
      // depende de ella para tener un indice que lo cubra (verificado
      // corriendo esto de verdad, no supuesto — fallo con "Cannot drop
      // index 'uk_ot_cosa': needed in a foreign key constraint" antes de
      // este fix). Se crea un indice simple de soporte en ot_id ANTES de
      // borrar el compuesto, para que el FK siga teniendo algo que lo cubra.
      if (!table.indices.some((i) => i.columnNames.length === 1 && i.columnNames[0] === "ot_id")) {
        await queryRunner.createIndex(
          "taller_ot_cosas_trae",
          new TableIndex({ name: "IDX_ot_cosas_trae_ot_id", columnNames: ["ot_id"] }),
        );
      }
      await queryRunner.dropIndex("taller_ot_cosas_trae", "uk_ot_cosa");
    }
    if (table.findColumnByName("observacion")) {
      await queryRunner.dropColumn("taller_ot_cosas_trae", "observacion");
    }
    if (table.findColumnByName("cantidad")) {
      await queryRunner.dropColumn("taller_ot_cosas_trae", "cantidad");
    }
  }
}
