import { Queue, Job } from 'bullmq';
import { MetadataRefreshJob, AudioAnalysisJob, CleanupJob, LibraryScanJob, ReindexTracksJob, ReprocessArtistsJob } from '../types/job.types.js';

export interface JobServiceConfig {
  redisUrl: string;
}

export const QUEUE_NAME = 'muzick-queue';

export interface QueueStats {
  waiting: number;
  active: number;
  completed: number;
  failed: number;
  delayed: number;
  paused: number;
}

export interface JobHistoryEntry {
  id: string;
  name: string;
  data: Record<string, unknown>;
  timestamp: number;
  finishedOn?: number;
  failedReason?: string;
  returnvalue?: unknown;
}

export class JobService {
  private queue: Queue;

  constructor(config: JobServiceConfig) {
    this.queue = new Queue(QUEUE_NAME, {
      connection: {
        url: config.redisUrl,
      },
    });
  }

  async enqueueMetadataRefresh(trackId: string, type: 'full' | 'partial') {
    const payload: MetadataRefreshJob = { trackId, refreshType: type };
    await this.queue.add('metadata_refresh', payload);
  }

  async enqueueAudioAnalysis(trackId: string, features: string[]) {
    const payload: AudioAnalysisJob = { trackId, features };
    await this.queue.add('audio_analysis', payload);
  }

  async enqueueCleanup(reason: 'expired' | 'manual', targetFiles: string[]) {
    const payload: CleanupJob = { reason, targetFiles };
    await this.queue.add('cleanup', payload);
  }

  async enqueueLibraryScan(directory: string) {
    const payload: LibraryScanJob = { directory };
    await this.queue.add('scan_library', payload);
  }

  async enqueueReindexTracks() {
    const payload: ReindexTracksJob = {};
    await this.queue.add('reindex_tracks', payload);
  }

  async enqueueReprocessArtists() {
    const payload: ReprocessArtistsJob = { batchSize: 100, offset: 0 };
    await this.queue.add('reprocess_artists', payload);
  }

  /**
   * Enqueue metadata_refresh jobs for a batch of track IDs. Used by the
   * /admin/reenrich-tracks endpoint to re-canonicalize metadata (artist names,
   * album titles, MBIDs, cover art) without re-scanning files from disk.
   *
   * Each job is deduped by `jobId: meta-<trackId>` so re-running the endpoint
   * doesn't stack duplicate jobs. Old completed/failed jobs with the same ID
   * are removed first so re-enrichment actually works (BullMQ otherwise treats
   * existing jobIds as duplicates and silently skips them).
   */
  async enqueueMetadataRefreshBatch(trackIds: string[]): Promise<number> {
    let enqueued = 0;
    for (const trackId of trackIds) {
      const jobId = `meta-${trackId}`;
      await this.queue.remove(jobId).catch(() => {});
      const payload: MetadataRefreshJob = { trackId, refreshType: 'full' };
      await this.queue.add('metadata_refresh', payload, {
        jobId,
        removeOnComplete: { age: 86400, count: 10000 },
        removeOnFail: { age: 86400, count: 10000 },
      });
      enqueued++;
    }
    return enqueued;
  }

  async getQueueStats(): Promise<QueueStats> {
    const [waiting, active, completed, failed, delayed] = await Promise.all([
      this.queue.getWaitingCount(),
      this.queue.getActiveCount(),
      this.queue.getCompletedCount(),
      this.queue.getFailedCount(),
      this.queue.getDelayedCount(),
    ]);
    return { waiting, active, completed, failed, delayed, paused: 0 };
  }

  async getJobHistory(limit = 100): Promise<JobHistoryEntry[]> {
    // Get jobs from completed and failed queues (most recent first)
    const [completedJobs, failedJobs] = await Promise.all([
      this.queue.getJobs(['completed'], 0, limit),
      this.queue.getJobs(['failed'], 0, limit),
    ]);

    const allJobs = [...completedJobs, ...failedJobs].map(job => ({
      id: job.id as string,
      name: job.name,
      data: job.data as Record<string, unknown>,
      timestamp: job.timestamp,
      finishedOn: job.finishedOn,
      failedReason: job.failedReason,
      returnvalue: job.returnvalue,
    }));

    // Sort by timestamp descending (most recent first)
    allJobs.sort((a, b) => b.timestamp - a.timestamp);

    return allJobs.slice(0, limit);
  }

  async close() {
    await this.queue.close();
  }
}
