/**
 * One-off cleanup: case-insensitive dedupe of artists AND albums.
 *
 * normalize_artist() preserves case, so case/punctuation variants of the same
 * artist ("Acryl madness" vs "Acryl Madness", "Booker" vs "BOOKER") were stored
 * as distinct rows. Each variant carried its own album, producing duplicate —
 * and often empty — albums (e.g. two "Before Neon 2", two "BROKESTAR").
 *
 * This:
 *   1. Merges artists sharing lower(normalized_name) into one keeper (prefers a
 *      row with an MBID, then an image, then the most track links, then oldest).
 *      track_artists / albums / artist_similar / artist_aliases are moved over;
 *      losing artist rows are deleted.
 *   2. Merges albums sharing (artist_id, lower(title)) into one keeper (prefers a
 *      row with artwork, then the most tracks, then oldest). Tracks are moved to
 *      the keeper BEFORE the empty duplicate is deleted (albums cascade-delete
 *      their tracks, so order matters).
 *   3. Drops redundant 'featured' track_artists where the same artist is already
 *      'main' on that track (case variants previously looked like a feature).
 *
 * Transactional + idempotent. Run:
 *   DATABASE_URL=... npx tsx src/scripts/dedup-artists-albums.ts [--dry-run]
 */
import { Client as PgClient } from 'pg';

const DRY_RUN = process.argv.includes('--dry-run');

async function main() {
  const pg = new PgClient({ connectionString: process.env.DATABASE_URL });
  await pg.connect();
  console.log(`[Dedup] Connected${DRY_RUN ? ' (DRY RUN)' : ''}`);

  if (!DRY_RUN) await pg.query('BEGIN');

  let artistsMerged = 0;
  let albumsMerged = 0;

  // --- 1. Artist dedupe (case-insensitive) ---------------------------------
  const artistGroups = await pg.query<{ ids: string[] }>(
    `SELECT array_agg(id ORDER BY
        CASE WHEN mbid IS NOT NULL THEN 0 ELSE 1 END,
        CASE WHEN image_path IS NOT NULL AND image_path <> '' THEN 0 ELSE 1 END,
        (SELECT count(*) FROM track_artists ta WHERE ta.artist_id = artists.id) DESC,
        created_at) AS ids
     FROM artists
     GROUP BY lower(normalized_name)
     HAVING count(*) > 1`
  );

  for (const { ids } of artistGroups.rows) {
    const keep = ids[0];
    for (const loser of ids.slice(1)) {
      if (DRY_RUN) { artistsMerged++; continue; }

      // track_artists: copy to keeper, drop loser's.
      await pg.query(
        `INSERT INTO track_artists (track_id, artist_id, role)
         SELECT track_id, $1, role FROM track_artists WHERE artist_id = $2
         ON CONFLICT (track_id, artist_id, role) DO NOTHING`,
        [keep, loser]
      );
      await pg.query(`DELETE FROM track_artists WHERE artist_id = $1`, [loser]);

      // artist_similar + aliases: move with conflict-skip.
      await pg.query(
        `INSERT INTO artist_similar (artist_id, similar_name, match, fetched_at)
         SELECT $1, similar_name, match, fetched_at FROM artist_similar WHERE artist_id = $2
         ON CONFLICT (artist_id, similar_name) DO NOTHING`,
        [keep, loser]
      );
      await pg.query(`DELETE FROM artist_similar WHERE artist_id = $1`, [loser]);
      await pg.query(
        `INSERT INTO artist_aliases (artist_id, alias)
         SELECT $1, alias FROM artist_aliases WHERE artist_id = $2
         ON CONFLICT (artist_id, alias) DO NOTHING`,
        [keep, loser]
      );
      await pg.query(`DELETE FROM artist_aliases WHERE artist_id = $1`, [loser]);

      // Albums: move to keeper where no same-title album exists; otherwise fold
      // tracks into the keeper's matching album, then drop the empty duplicate.
      const loserAlbums = await pg.query<{ id: string; title: string }>(
        `SELECT id, title FROM albums WHERE artist_id = $1`,
        [loser]
      );
      for (const la of loserAlbums.rows) {
        const match = await pg.query<{ id: string }>(
          `SELECT id FROM albums WHERE artist_id = $1 AND lower(title) = lower($2) LIMIT 1`,
          [keep, la.title]
        );
        if (match.rows.length === 0) {
          await pg.query(`UPDATE albums SET artist_id = $1 WHERE id = $2`, [keep, la.id]);
        } else {
          await foldAlbum(pg, match.rows[0].id, la.id);
        }
      }

      // Carry over an image if the keeper lacks one.
      await pg.query(
        `UPDATE artists k SET image_path = l.image_path
         FROM artists l
         WHERE k.id = $1 AND l.id = $2
           AND (k.image_path IS NULL OR k.image_path = '')
           AND l.image_path IS NOT NULL AND l.image_path <> ''`,
        [keep, loser]
      );

      await pg.query(`DELETE FROM artists WHERE id = $1`, [loser]);
      artistsMerged++;
    }
  }

  // --- 2. Album dedupe (case-insensitive, within an artist) -----------------
  const albumGroups = await pg.query<{ ids: string[] }>(
    `SELECT array_agg(id ORDER BY
        CASE WHEN artwork_id IS NOT NULL AND artwork_id <> '' THEN 0 ELSE 1 END,
        (SELECT count(*) FROM tracks t WHERE t.album_id = albums.id) DESC,
        id) AS ids
     FROM albums
     WHERE artist_id IS NOT NULL
     GROUP BY artist_id, lower(title)
     HAVING count(*) > 1`
  );
  for (const { ids } of albumGroups.rows) {
    const keep = ids[0];
    for (const loser of ids.slice(1)) {
      if (DRY_RUN) { albumsMerged++; continue; }
      await foldAlbum(pg, keep, loser);
      albumsMerged++;
    }
  }

  // --- 3. Drop redundant 'featured' where the artist is also 'main' ---------
  let redundant = 0;
  if (!DRY_RUN) {
    const r = await pg.query(
      `DELETE FROM track_artists f
       WHERE f.role = 'featured'
         AND EXISTS (SELECT 1 FROM track_artists m
                     WHERE m.track_id = f.track_id AND m.artist_id = f.artist_id AND m.role = 'main')`
    );
    redundant = r.rowCount ?? 0;
  }

  if (!DRY_RUN) await pg.query('COMMIT');
  console.log(`[Dedup] Done: artistsMerged=${artistsMerged} albumsMerged=${albumsMerged} redundantFeatured=${redundant}`);
  await pg.end();
}

/** Move all tracks + (missing) artwork from loserAlbum into keeperAlbum, then delete loserAlbum. */
async function foldAlbum(pg: PgClient, keeperId: string, loserId: string): Promise<void> {
  if (keeperId === loserId) return;
  await pg.query(`UPDATE tracks SET album_id = $1 WHERE album_id = $2`, [keeperId, loserId]);
  await pg.query(
    `UPDATE albums k SET artwork_id = l.artwork_id, year = COALESCE(k.year, l.year)
     FROM albums l
     WHERE k.id = $1 AND l.id = $2
       AND (k.artwork_id IS NULL OR k.artwork_id = '')
       AND l.artwork_id IS NOT NULL AND l.artwork_id <> ''`,
    [keeperId, loserId]
  );
  await pg.query(`DELETE FROM albums WHERE id = $1`, [loserId]);
}

main()
  .then(() => process.exit(0))
  .catch((err) => {
    console.error('[Dedup] Failed:', err);
    process.exit(1);
  });
