// MusicRoom - yt-dlp integration module
// Handles fetching audio from URLs via yt-dlp

import { spawn } from "child_process";
import { join } from "path";
import {
  saveSlowQueueItem,
  updateSlowQueueItem,
  loadSlowQueue,
  deleteSlowQueueItem,
  clearCompletedSlowQueue,
  addTracksToPlaylist,
  type SlowQueueRow
} from "./db";

export interface QueueItem {
  id: string;
  url: string;
  title: string;
  userId: number;
  status: "queued" | "downloading" | "complete" | "error" | "cancelled";
  progress: number;
  queueType: "fast" | "slow";
  error?: string;
  filename?: string;
  createdAt: number;
  completedAt?: number;
  playlistId?: string;
  playlistName?: string;
  position?: number;
  trackId?: string;  // Set after successful download
}

export interface YtdlpStatus {
  available: boolean;
  enabled: boolean;
  version: string | null;
  ffmpeg: boolean;
}

export interface PlaylistInfo {
  type: "playlist";
  title: string;
  count: number;
  items: { id: string; url: string; title: string }[];
  requiresConfirmation: true;
}

export interface SingleVideoInfo {
  type: "single";
  id: string;
  title: string;
  url: string;
}

type ProgressCallback = (item: QueueItem) => void;

// Configuration
let ytdlpCommand = "yt-dlp";
let ffmpegCommand = "ffmpeg";
let musicDir = "./music";
let fastQueueConcurrent = 2;
let slowQueueInterval = 180;
let allowPlaylists = true;

// Status
let ytdlpAvailable = false;
let ytdlpVersion: string | null = null;
let ffmpegAvailable = false;
let featureEnabled = false;

// Queues
const fastQueue: QueueItem[] = [];
const slowQueue: QueueItem[] = [];
let activeDownloads = 0;
let slowQueueTimer: ReturnType<typeof setTimeout> | null = null;
let lastSlowDownload = 0;

// Callbacks
let onProgress: ProgressCallback | null = null;
let onTrackReady: ((item: QueueItem) => void) | null = null;

// Generate unique ID
function generateId(): string {
  return Math.random().toString(36).substring(2, 10);
}

// Initialize ytdlp module
export async function initYtdlp(config: {
  enabled: boolean;
  command: string;
  ffmpegCommand: string;
  musicDir: string;
  fastQueueConcurrent: number;
  slowQueueInterval: number;
  allowPlaylists: boolean;
}): Promise<YtdlpStatus> {
  featureEnabled = config.enabled;
  ytdlpCommand = config.command;
  ffmpegCommand = config.ffmpegCommand;
  musicDir = config.musicDir;
  fastQueueConcurrent = config.fastQueueConcurrent;
  slowQueueInterval = config.slowQueueInterval;
  allowPlaylists = config.allowPlaylists;

  if (!featureEnabled) {
    console.log("[ytdlp] Feature disabled in config");
    return { available: false, enabled: false, version: null, ffmpeg: false };
  }

  // Check yt-dlp availability
  try {
    ytdlpVersion = await runCommand(ytdlpCommand, ["--version"]);
    ytdlpAvailable = true;
    console.log(`[ytdlp] Found yt-dlp version: ${ytdlpVersion.trim()}`);
  } catch (e) {
    console.error(`[ytdlp] yt-dlp not found (command: ${ytdlpCommand})`);
    ytdlpAvailable = false;
    featureEnabled = false;
  }

  // Check ffmpeg availability
  try {
    await runCommand(ffmpegCommand, ["-version"]);
    ffmpegAvailable = true;
    console.log("[ytdlp] ffmpeg available");
  } catch (e) {
    console.warn("[ytdlp] ffmpeg not found - audio extraction may fail");
    ffmpegAvailable = false;
  }

  // Load persisted slow queue from database
  if (featureEnabled) {
    const savedQueue = loadSlowQueue();
    for (const row of savedQueue) {
      slowQueue.push(rowToQueueItem(row));
    }
    if (savedQueue.length > 0) {
      console.log(`[ytdlp] Restored ${savedQueue.length} items from slow queue`);
    }
  }

  // Start slow queue processor
  if (featureEnabled) {
    startSlowQueueProcessor();
  }

  return getStatus();
}

// Convert database row to QueueItem
function rowToQueueItem(row: SlowQueueRow): QueueItem {
  return {
    id: row.id,
    url: row.url,
    title: row.title,
    userId: row.user_id,
    status: row.status as QueueItem["status"],
    progress: row.progress,
    queueType: "slow",
    error: row.error ?? undefined,
    createdAt: row.created_at * 1000,
    completedAt: row.completed_at ? row.completed_at * 1000 : undefined,
    playlistId: row.playlist_id ?? undefined,
    playlistName: row.playlist_name ?? undefined,
    position: row.position ?? undefined
  };
}

// Run a command and return stdout
function runCommand(cmd: string, args: string[]): Promise<string> {
  const fullCmd = `${cmd} ${args.join(" ")}`;
  console.log(`[ytdlp] Running: ${fullCmd}`);
  return new Promise((resolve, reject) => {
    const proc = spawn(cmd, args);
    let stdout = "";
    let stderr = "";
    proc.stdout.on("data", (data) => { stdout += data; });
    proc.stderr.on("data", (data) => { stderr += data; });
    proc.on("close", (code) => {
      console.log(`[ytdlp] Command exited with code ${code}`);
      if (code === 0) resolve(stdout);
      else reject(new Error(stderr || `Exit code ${code}`));
    });
    proc.on("error", reject);
  });
}

// Get current status
export function getStatus(): YtdlpStatus {
  return {
    available: ytdlpAvailable,
    enabled: featureEnabled,
    version: ytdlpVersion,
    ffmpeg: ffmpegAvailable
  };
}

// Check if feature is enabled and available
export function isAvailable(): boolean {
  return featureEnabled && ytdlpAvailable;
}

// Set progress callback
export function setProgressCallback(callback: ProgressCallback): void {
  onProgress = callback;
}

// Set track ready callback (called when download completes and needs playlist association)
export function setTrackReadyCallback(callback: (item: QueueItem) => void): void {
  onTrackReady = callback;
}

// Get all queue items
export function getQueues(): { fastQueue: QueueItem[]; slowQueue: QueueItem[]; slowQueueNextIn: number } {
  const now = Date.now();
  const nextIn = Math.max(0, Math.floor((lastSlowDownload + slowQueueInterval * 1000 - now) / 1000));
  return {
    fastQueue: [...fastQueue],
    slowQueue: [...slowQueue],
    slowQueueNextIn: nextIn
  };
}

// Get queue items for a specific user
export function getUserQueues(userId: number): { fastQueue: QueueItem[]; slowQueue: QueueItem[]; slowQueueNextIn: number } {
  const queues = getQueues();
  return {
    fastQueue: queues.fastQueue.filter(i => i.userId === userId),
    slowQueue: queues.slowQueue.filter(i => i.userId === userId),
    slowQueueNextIn: queues.slowQueueNextIn
  };
}

// Check URL and detect if it's a playlist
export async function checkUrl(url: string): Promise<PlaylistInfo | SingleVideoInfo> {
  const args = ["--flat-playlist", "--dump-json", "--no-warnings", url];
  const output = await runCommand(ytdlpCommand, args);
  
  // Parse JSON lines
  const lines = output.trim().split("\n").filter(l => l);
  
  if (lines.length === 0) {
    throw new Error("No video found");
  }
  
  if (lines.length === 1) {
    const data = JSON.parse(lines[0]);
    if (data._type === "playlist") {
      // It's a playlist with entries
      const items = (data.entries || []).map((e: any) => ({
        id: generateId(),
        url: e.url || e.webpage_url || `https://youtube.com/watch?v=${e.id}`,
        title: e.title || "Unknown"
      }));
      return {
        type: "playlist",
        title: data.title || "Playlist",
        count: items.length,
        items,
        requiresConfirmation: true
      };
    } else {
      // Single video
      return {
        type: "single",
        id: generateId(),
        title: data.title || "Unknown",
        url
      };
    }
  } else {
    // Multiple JSON lines = playlist
    const items = lines.map(line => {
      const data = JSON.parse(line);
      return {
        id: generateId(),
        url: data.url || data.webpage_url || url,
        title: data.title || "Unknown"
      };
    });
    return {
      type: "playlist",
      title: "Playlist",
      count: items.length,
      items,
      requiresConfirmation: true
    };
  }
}

// Add single video to fast queue
export function addToFastQueue(url: string, title: string, userId: number): QueueItem {
  const item: QueueItem = {
    id: generateId(),
    url,
    title,
    userId,
    status: "queued",
    progress: 0,
    queueType: "fast",
    createdAt: Date.now()
  };
  fastQueue.push(item);
  processNextFast();
  return item;
}

// Add items to slow queue (for playlists)
export function addToSlowQueue(
  items: { url: string; title: string }[],
  userId: number,
  playlist?: { id: string; name: string }
): QueueItem[] {
  const now = Date.now();
  const queueItems: QueueItem[] = items.map((item, index) => ({
    id: generateId(),
    url: item.url,
    title: item.title,
    userId,
    status: "queued" as const,
    progress: 0,
    queueType: "slow" as const,
    createdAt: now,
    playlistId: playlist?.id,
    playlistName: playlist?.name,
    position: playlist ? index : undefined
  }));
  
  // Persist to database
  for (const item of queueItems) {
    saveSlowQueueItem({
      id: item.id,
      url: item.url,
      title: item.title,
      userId: item.userId,
      status: item.status,
      progress: item.progress,
      playlistId: item.playlistId,
      playlistName: item.playlistName,
      position: item.position,
      createdAt: Math.floor(item.createdAt / 1000)
    });
  }
  
  slowQueue.push(...queueItems);
  return queueItems;
}

// Process next item in fast queue
function processNextFast(): void {
  if (activeDownloads >= fastQueueConcurrent) return;
  
  const item = fastQueue.find(i => i.status === "queued");
  if (!item) return;
  
  activeDownloads++;
  downloadItem(item).finally(() => {
    activeDownloads--;
    processNextFast();
  });
}

// Start slow queue processor
function startSlowQueueProcessor(): void {
  if (slowQueueTimer) return;
  
  const processNext = () => {
    const item = slowQueue.find(i => i.status === "queued");
    if (item) {
      lastSlowDownload = Date.now();
      downloadItem(item).finally(() => {
        slowQueueTimer = setTimeout(processNext, slowQueueInterval * 1000);
      });
    } else {
      slowQueueTimer = setTimeout(processNext, 5000); // Check again in 5s
    }
  };
  
  // Start immediately if there are items
  const hasQueued = slowQueue.some(i => i.status === "queued");
  if (hasQueued) {
    processNext();
  } else {
    slowQueueTimer = setTimeout(processNext, 5000);
  }
}

// Download a single item
async function downloadItem(item: QueueItem): Promise<void> {
  item.status = "downloading";
  item.progress = 0;
  notifyProgress(item);
  
  console.log(`[ytdlp] Starting download: ${item.title} (${item.url})`);
  
  try {
    const outputTemplate = join(musicDir, "%(title)s.%(ext)s");
    const args = [
      "-x",
      "--audio-format", "mp3",
      "-o", outputTemplate,
      "--progress",
      "--newline",
      "--no-warnings",
      item.url
    ];
    
    const fullCmd = `${ytdlpCommand} ${args.join(" ")}`;
    console.log(`[ytdlp] Running: ${fullCmd}`);
    
    await new Promise<void>((resolve, reject) => {
      const proc = spawn(ytdlpCommand, args);
      
      proc.stdout.on("data", (data) => {
        const line = data.toString();
        console.log(`[ytdlp] ${line.trim()}`);
        // Parse progress from yt-dlp output
        const match = line.match(/(\d+\.?\d*)%/);
        if (match) {
          item.progress = parseFloat(match[1]);
          notifyProgress(item);
        }
      });
      
      proc.stderr.on("data", (data) => {
        console.error(`[ytdlp] stderr: ${data}`);
      });
      
      proc.on("close", (code) => {
        console.log(`[ytdlp] Download finished with code ${code}`);
        if (code === 0) resolve();
        else reject(new Error(`yt-dlp exited with code ${code}`));
      });
      
      proc.on("error", reject);
    });
    
    console.log(`[ytdlp] Complete: ${item.title}`);
    item.status = "complete";
    item.progress = 100;
    item.completedAt = Date.now();
    
    // Update database
    if (item.queueType === "slow") {
      updateSlowQueueItem(item.id, {
        status: "complete",
        progress: 100,
        completedAt: Math.floor(item.completedAt / 1000)
      });
      
      // Register for playlist addition immediately - library will match when it scans
      if (item.playlistId && onTrackReady) {
        onTrackReady(item);
      }
    }
    
    notifyProgress(item);
    
    // Remove from queue after delay
    setTimeout(() => removeFromQueue(item), 5000);
    
  } catch (e: any) {
    item.status = "error";
    item.error = e.message || "Download failed";
    item.completedAt = Date.now();
    
    // Update database
    if (item.queueType === "slow") {
      updateSlowQueueItem(item.id, {
        status: "error",
        error: item.error,
        completedAt: Math.floor(item.completedAt / 1000)
      });
    }
    
    notifyProgress(item);
    
    // Remove from queue after delay
    setTimeout(() => removeFromQueue(item), 10000);
  }
}

// Remove item from queue
function removeFromQueue(item: QueueItem): void {
  if (item.queueType === "fast") {
    const idx = fastQueue.findIndex(i => i.id === item.id);
    if (idx !== -1) fastQueue.splice(idx, 1);
  } else {
    const idx = slowQueue.findIndex(i => i.id === item.id);
    if (idx !== -1) slowQueue.splice(idx, 1);
    // Remove from database
    deleteSlowQueueItem(item.id);
  }
}

// Cancel a slow queue item
export function cancelSlowQueueItem(id: string, userId: number): boolean {
  const item = slowQueue.find(i => i.id === id && i.userId === userId);
  if (!item || item.status === "downloading") {
    return false; // Can't cancel if not found, not owned, or already downloading
  }
  
  item.status = "cancelled";
  item.completedAt = Date.now();
  
  // Update database
  updateSlowQueueItem(id, {
    status: "cancelled",
    completedAt: Math.floor(item.completedAt / 1000)
  });
  
  notifyProgress(item);
  
  // Remove from queue after brief delay
  setTimeout(() => removeFromQueue(item), 1000);
  
  return true;
}

// Cancel all queued items in slow queue for a user
export function cancelAllSlowQueueItems(userId: number): number {
  const items = slowQueue.filter(i => i.userId === userId && i.status === "queued");
  let cancelled = 0;
  
  for (const item of items) {
    item.status = "cancelled";
    item.completedAt = Date.now();
    
    updateSlowQueueItem(item.id, {
      status: "cancelled",
      completedAt: Math.floor(item.completedAt / 1000)
    });
    
    notifyProgress(item);
    cancelled++;
  }
  
  // Remove all cancelled items after brief delay
  setTimeout(() => {
    for (let i = slowQueue.length - 1; i >= 0; i--) {
      if (slowQueue[i].status === "cancelled") {
        slowQueue.splice(i, 1);
      }
    }
  }, 1000);
  
  return cancelled;
}

// Notify progress callback
function notifyProgress(item: QueueItem): void {
  if (onProgress) {
    onProgress(item);
  }
}

// Mark a slow queue item as skipped (already in library)
export function skipSlowQueueItem(id: string, trackId: string): QueueItem | null {
  const item = slowQueue.find(i => i.id === id && i.status === "queued");
  if (!item) return null;
  
  item.status = "complete";
  item.progress = 100;
  item.completedAt = Date.now();
  item.trackId = trackId;
  
  // Update database
  updateSlowQueueItem(id, {
    status: "complete",
    progress: 100,
    completedAt: Math.floor(item.completedAt / 1000)
  });
  
  notifyProgress(item);
  
  // Remove from queue after brief delay
  setTimeout(() => removeFromQueue(item), 1000);
  
  return item;
}

// Get queued items from slow queue (for prescan)
export function getQueuedSlowItems(): QueueItem[] {
  return slowQueue.filter(i => i.status === "queued");
}

// Cleanup old completed/failed items
export function cleanupOldItems(maxAge: number = 3600000): void {
  const now = Date.now();
  const cleanup = (queue: QueueItem[]) => {
    for (let i = queue.length - 1; i >= 0; i--) {
      const item = queue[i];
      if ((item.status === "complete" || item.status === "error" || item.status === "cancelled") && 
          now - item.createdAt > maxAge) {
        queue.splice(i, 1);
      }
    }
  };
  cleanup(fastQueue);
  cleanup(slowQueue);
  
  // Also cleanup database
  clearCompletedSlowQueue(Math.floor(maxAge / 1000));
}
