import { FastifyInstance } from 'fastify';
import { DbService } from '../services/db.service.js';
import { DiscoveryService } from '../services/discovery.service.js';
import { ImageEnrichmentService } from '../services/image-enrichment.service.js';
import { JobService } from '../services/job.service.js';

export default async function discoveryRoutes(fastify: FastifyInstance, options: { dbService: DbService; jobService: JobService }) {
  const { dbService } = options;
  const { jobService } = options;
  const discovery = new DiscoveryService(dbService);
  const images = new ImageEnrichmentService(dbService);

  /**
   * POST /api/discovery/walk — trigger graph walk for discovery candidates
   */
  fastify.post('/discovery/walk', async (request, reply) => {
    const userId = (request.headers['x-user-id'] as string) || '00000000-0000-0000-0000-000000000000';
    const count = await discovery.walkGraphForDiscovery(userId);
    return reply.send({ newCandidates: count });
  });

  /**
   * GET /api/discovery/candidates — list discovery candidates
   * Query: ?status=candidate&limit=50
   */
  fastify.get('/discovery/candidates', async (request, reply) => {
    const query = request.query as { status?: string; limit?: string };
    const status = query.status || 'candidate';
    const limit = parseInt(query.limit || '50', 10);

    const res = await dbService.pgClient.query(
      `SELECT * FROM discovery_candidates WHERE status = $1 ORDER BY first_seen_at DESC LIMIT $2`,
      [status, limit]
    );
    return reply.send({ candidates: res.rows });
  });

  /**
   * GET /api/discovery/overview — every recommendation and what became of it.
   *
   * One row per candidate, acquired or not, so a stalled candidate is as
   * visible as a retained track. The play/skip counts come from `evidence`
   * rather than tracks.play_count because the probation sweep decides on
   * exactly those two signals: showing anything else would be showing a number
   * that does not drive the outcome.
   */
  fastify.get('/discovery/overview', async (request, reply) => {
    const query = request.query as { limit?: string };
    const limit = Math.min(Math.max(parseInt(query.limit || '200', 10) || 200, 1), 500);

    const rows = await dbService.pgClient.query(
      `SELECT dc.id, dc.source, dc.status, dc.title, dc.artist_credit, dc.notes,
              dc.first_seen_at, dc.acquired_at, dc.last_error, dc.acquisition_attempts,
              t.id AS track_id, t.title AS track_title, t.artist AS track_artist,
              t.probation_status, t.probation_entered_at,
              (SELECT COUNT(*)::int FROM evidence e
                WHERE e.entity_type = 'track' AND e.entity_id = t.id
                  AND e.signal = 'playback_completed') AS completed_plays,
              (SELECT COUNT(*)::int FROM evidence e
                WHERE e.entity_type = 'track' AND e.entity_id = t.id
                  AND e.signal = 'skip_quick') AS quick_skips
         FROM discovery_candidates dc
         LEFT JOIN tracks t ON t.id = dc.acquired_track_id
        ORDER BY COALESCE(dc.acquired_at, dc.first_seen_at) DESC
        LIMIT $1`,
      [limit]
    );

    const summary = await dbService.pgClient.query(
      `SELECT dc.source,
              COUNT(*)::int AS candidates,
              COUNT(t.id) FILTER (WHERE t.probation_status = 'probation')::int AS probation,
              COUNT(t.id) FILTER (WHERE t.probation_status = 'retained')::int  AS retained,
              COUNT(t.id) FILTER (WHERE t.probation_status = 'retired')::int   AS retired,
              COUNT(*) FILTER (WHERE dc.acquired_track_id IS NULL
                                 AND dc.status <> 'candidate')::int AS stalled
         FROM discovery_candidates dc
         LEFT JOIN tracks t ON t.id = dc.acquired_track_id
        GROUP BY dc.source
        ORDER BY dc.source`
    );

    return reply.send({ rows: rows.rows, summary: summary.rows });
  });

  /**
   * POST /api/discovery/eval — evaluate pending candidates for acquisition
   */
  fastify.post('/discovery/eval', async (request, reply) => {
    const userId = (request.headers['x-user-id'] as string) || '00000000-0000-0000-0000-000000000000';
    const results = await discovery.evalCandidates(userId);
    for (const result of results) {
      if (!result.shouldAcquire) continue;
      try {
        await jobService.enqueueDiscoveryAcquisition(result.candidateId);
      } catch (err) {
        const reason = err instanceof Error ? err.message : 'failed to enqueue acquisition';
        await discovery.markEnqueueFailed(result.candidateId, reason);
        result.shouldAcquire = false;
        result.reason = `queue unavailable: ${reason}`;
      }
    }
    return reply.send({ evaluated: results.length, results });
  });

  /**
   * POST /api/discovery/sweep-probation — evaluate probation tracks
   */
  fastify.post('/discovery/sweep-probation', async (_request, reply) => {
    const result = await discovery.sweepProbation();
    return reply.send(result);
  });

  /**
   * POST /api/discovery/meta-learn — run meta-learning
   */
  fastify.post('/discovery/meta-learn', async (_request, reply) => {
    await discovery.runMetaLearning();
    return reply.send({ status: 'ok' });
  });

  /**
   * POST /api/images/fetch — mark image candidates for an entity
   * Body: { entity_type, entity_id }
   */
  fastify.post('/images/fetch', async (request, reply) => {
    const body = request.body as { entity_type: string; entity_id: string };
    if (!body.entity_type || !body.entity_id) {
      return reply.code(400).send({ error: 'entity_type and entity_id required' });
    }

    let count = 0;
    if (body.entity_type === 'artist') {
      count = await images.fetchImagesForArtist(body.entity_id);
    } else if (body.entity_type === 'album') {
      count = await images.fetchImagesForAlbum(body.entity_id);
    } else {
      return reply.code(400).send({ error: 'entity_type must be "artist" or "album"' });
    }
    return reply.send({ candidateRows: count });
  });

  /**
   * POST /api/images/select — select best image for an entity
   * Body: { entity_type, entity_id }
   */
  fastify.post('/images/select', async (request, reply) => {
    const body = request.body as { entity_type: string; entity_id: string };
    if (!body.entity_type || !body.entity_id) {
      return reply.code(400).send({ error: 'entity_type and entity_id required' });
    }
    const url = await images.selectBestImage(body.entity_type, body.entity_id);
    return reply.send({ url });
  });
}
