import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { JobService } from '../services/job.service.js';
import { DbService } from '../services/db.service.js';

export default async function adminRoutes(fastify: FastifyInstance, options: { jobService: JobService; dbService: DbService }) {
  const { jobService, dbService } = options;

  fastify.post('/scan', async (request: FastifyRequest, reply: FastifyReply) => {
    const { directory } = request.body as { directory: string };
    if (!directory) {
      return reply.code(400).send({ error: 'Directory is required' });
    }
    await jobService.enqueueLibraryScan(directory);
    return { status: 'Scan job enqueued', directory };
  });

  fastify.post('/reindex-tracks', async (_request: FastifyRequest, reply: FastifyReply) => {
    await jobService.enqueueReindexTracks();
    return { status: 'Reindex job enqueued' };
  });

  fastify.post('/reprocess-artists', async (_request: FastifyRequest, reply: FastifyReply) => {
    await jobService.enqueueReprocessArtists();
    return { status: 'Artist reprocessing job enqueued' };
  });

  fastify.post('/dedup-albums', async (_request: FastifyRequest, reply: FastifyReply) => {
    // Merge duplicate album rows directly (synchronous — it's just SQL, no
    // external API calls). Returns the number of albums merged away.
    // Tiebreaker for keeper selection: MBID > artwork > earliest release_date
    // > most tracks > oldest created_at.
    //
    // Note: the two duplicate-detection passes (by title and by MBID) may find
    // overlapping pairs; the UNION ALL in `pairs` can produce duplicates, but
    // the DELETE at the end is idempotent (a loser deleted in one pair won't
    // exist for the next). The folded/moved CTEs also tolerate this because
    // COALESCE is idempotent and the loser row simply won't be found again.
    const res = await dbService.pgClient.query<{ count: number }>(`
      WITH duplicates AS (
        SELECT lower(title) AS lt, array_agg(id ORDER BY
          CASE WHEN mbid IS NOT NULL THEN 0 ELSE 1 END,
          CASE WHEN artwork_id IS NOT NULL AND artwork_id <> '' THEN 0 ELSE 1 END,
          release_date NULLS LAST,
          (SELECT COUNT(*) FROM tracks t WHERE t.album_id = albums.id) DESC,
          created_at
        ) AS ids
        FROM albums GROUP BY lower(title) HAVING COUNT(*) > 1
      ),
      mbid_dupes AS (
        SELECT mbid, array_agg(id ORDER BY
          CASE WHEN artwork_id IS NOT NULL AND artwork_id <> '' THEN 0 ELSE 1 END,
          release_date NULLS LAST,
          created_at
        ) AS ids
        FROM albums WHERE mbid IS NOT NULL
        GROUP BY mbid HAVING COUNT(*) > 1
      ),
      pairs AS (
        SELECT ids[1] AS keep_id, unnest(ids[2:]) AS loser_id FROM duplicates
        UNION
        SELECT ids[1] AS keep_id, unnest(ids[2:]) AS loser_id FROM mbid_dupes
      ),
      -- Fold metadata from losers onto keepers (idempotent via COALESCE).
      folded AS (
        UPDATE albums a SET
          artwork_id = COALESCE(a.artwork_id, src.artwork_id),
          year = COALESCE(a.year, src.year),
          mbid = COALESCE(a.mbid, src.mbid),
          release_date = COALESCE(a.release_date, src.release_date)
        FROM (
          SELECT DISTINCT ON (p.loser_id) p.keep_id, lo.artwork_id, lo.year, lo.mbid, lo.release_date, p.loser_id
          FROM pairs p
          JOIN albums lo ON lo.id = p.loser_id
          ORDER BY p.loser_id
        ) AS src
        WHERE a.id = src.keep_id
      ),
      -- Move tracks from losers to keepers.
      moved AS (
        UPDATE tracks SET album_id = src.keep_id
        FROM (SELECT DISTINCT keep_id, loser_id FROM pairs) AS src
        WHERE tracks.album_id = src.loser_id
      ),
      -- Delete losers.
      deleted AS (
        DELETE FROM albums
        WHERE id IN (SELECT DISTINCT loser_id FROM pairs)
        RETURNING 1
      )
      SELECT COUNT(*)::int AS count FROM deleted
    `);
    return { status: 'Albums deduplicated', merged: res.rows[0]?.count ?? 0 };
  });

  fastify.post('/reenrich-tracks', async (_request: FastifyRequest, reply: FastifyReply) => {
    // Re-enqueue metadata_refresh for every LIBRARY track without re-reading
    // files from disk. This re-runs the MusicBrainz canonicalisation (artist
    // names, album titles, MBIDs) and re-triggers album_cover jobs — much
    // faster than a full scan when only metadata needs refreshing.
    const res = await dbService.pgClient.query<{ id: string }>(
      `SELECT id FROM tracks WHERE state = 'LIBRARY' ORDER BY id`
    );
    const trackIds = res.rows.map((r) => r.id);
    const enqueued = await jobService.enqueueMetadataRefreshBatch(trackIds);
    return { status: 'Re-enrich enqueued', trackCount: enqueued };
  });

  fastify.get('/queue-stats', async () => {
    return await jobService.getQueueStats();
  });

  fastify.get('/job-history', async (request: FastifyRequest) => {
    const { limit } = request.query as { limit?: string };
    return await jobService.getJobHistory(parseInt(limit || '100', 10));
  });

  fastify.get('/duplicates', async (request) => {
    const { mode } = request.query as { mode?: string };
    return await dbService.getDuplicateGroups(mode === 'title-artist' ? 'title-artist' : 'hash');
  });

  fastify.post('/duplicates/merge', async (request: FastifyRequest, reply: FastifyReply) => {
    const { keepId, deleteIds } = request.body as { keepId: string; deleteIds: string[] };
    if (!keepId || !Array.isArray(deleteIds) || deleteIds.length === 0) {
      return reply.code(400).send({ error: 'keepId and deleteIds[] are required' });
    }
    await dbService.mergeDuplicates(keepId, deleteIds);
    return { status: 'merged', kept: keepId, deleted: deleteIds.length };
  });

  fastify.get('/artist-stats', async (request: FastifyRequest, reply: FastifyReply) => {
    const db = dbService.pgClient;
    
    const total = await db.query('SELECT COUNT(*)::int AS n FROM artists');
    const withMbid = await db.query('SELECT COUNT(*)::int AS n FROM artists WHERE mbid IS NOT NULL');
    const withCanonical = await db.query('SELECT COUNT(*)::int AS n FROM artists WHERE canonical_name IS NOT NULL');
    const withSort = await db.query('SELECT COUNT(*)::int AS n FROM artists WHERE sort_name IS NOT NULL');
    const withImage = await db.query('SELECT COUNT(*)::int AS n FROM artists WHERE image_path IS NOT NULL AND image_path != \'\'');
    const aliases = await db.query('SELECT COUNT(*)::int AS n FROM artist_aliases');
    const cache = await db.query('SELECT COUNT(*)::int AS n FROM artist_lookup_cache');
    
    const noImage = await db.query(`
      SELECT name, canonical_name, mbid, sort_name 
      FROM artists 
      WHERE image_path IS NULL OR image_path = ''
      ORDER BY name
      LIMIT 50
    `);

    return {
      total: total.rows[0].n,
      withMbid: withMbid.rows[0].n,
      withCanonicalName: withCanonical.rows[0].n,
      withSortName: withSort.rows[0].n,
      withImage: withImage.rows[0].n,
      withoutImage: total.rows[0].n - withImage.rows[0].n,
      aliases: aliases.rows[0].n,
      cacheSize: cache.rows[0].n,
      imageCoverage: `${((withImage.rows[0].n / total.rows[0].n) * 100).toFixed(1)}%`,
      artistsWithoutImage: noImage.rows,
    };
  });

  fastify.get('/artist-verify/:name', async (request: FastifyRequest, reply: FastifyReply) => {
    const { name } = request.params as { name: string };
    const db = dbService.pgClient;
    
    const exact = await db.query(
      `SELECT id, name, canonical_name, sort_name, mbid, image_path 
       FROM artists WHERE name = $1`,
      [name]
    );
    
    const normalized = await db.query(
      `SELECT id, name, canonical_name, sort_name, mbid, image_path 
       FROM artists WHERE normalize_artist(name) = normalize_artist($1)`,
      [name]
    );
    
    const aliases = await db.query(
      `SELECT a.*, ar.canonical_name as artist_canonical, ar.mbid as artist_mbid
       FROM artist_aliases a
       JOIN artists ar ON ar.id = a.artist_id
       WHERE a.alias_normalized = normalize_artist($1)`,
      [name]
    );

    return {
      exactMatch: exact.rows,
      normalizedMatches: normalized.rows,
      aliases: aliases.rows,
    };
  });
}
