import { v } from "convex/values";
import { internalMutation, mutation } from "./_generated/server";
import { internal } from "./_generated/api";
import { getAuthUserId } from "@convex-dev/auth/server";

// ========== SERVER-DRIVEN PLAYBACK ==========

// Advance to next track in radio queue
export const advanceToNextTrack = internalMutation({
  args: { roomId: v.id("rooms") },
  handler: async (ctx, args) => {
    const room = await ctx.db.get(args.roomId);
    if (!room || room.type !== "radio") return;
    if (!room.playbackState) return;

    const { queue, queueIndex, nextAdvanceJobId } = room.playbackState;

    // Cancel any existing scheduled advance job to prevent duplicates
    if (nextAdvanceJobId) {
      try {
        await ctx.scheduler.cancel(nextAdvanceJobId);
      } catch (error) {
        // Job may have already completed or been cancelled
        console.log(`[advanceToNextTrack] Could not cancel job ${nextAdvanceJobId}`);
      }
    }

    // Check if there's a next track
    if (queueIndex + 1 >= queue.length) {
      // End of queue - stop playback
      await ctx.db.patch(args.roomId, {
        playbackState: {
          ...room.playbackState,
          currentTrackId: undefined,
          isPlaying: false,
          position: 0,
          timestamp: Date.now(),
          startedAt: undefined,
          trackDuration: undefined,
          nextAdvanceJobId: undefined, // Clear the job ID
        },
      });

      // Trigger queue refill
      await ctx.scheduler.runAfter(
        0,
        internal.radioRooms.checkAndGenerateRadioSongs,
        { roomId: args.roomId }
      );
      return;
    }

    // Pop the current track from queue and move to next
    const newQueue = queue.slice(1); // Remove first item
    const nextTrackId = newQueue[0];
    const nextTrack = await ctx.db.get(nextTrackId);

    if (!nextTrack) {
      // Track not found, remove it and try again
      await ctx.db.patch(args.roomId, {
        playbackState: {
          ...room.playbackState,
          queue: newQueue.slice(1),
          queueIndex: 0,
        },
      });
      // Try advancing again
      await ctx.scheduler.runAfter(0, internal.radioPlayback.advanceToNextTrack, {
        roomId: args.roomId,
      });
      return;
    }

    // Check if track is ready to play
    if (
      nextTrack.status === "pending" ||
      nextTrack.status === "processing"
    ) {
      // Track not ready yet, wait a bit and try again
      await ctx.scheduler.runAfter(5000, internal.radioPlayback.advanceToNextTrack, {
        roomId: args.roomId,
      });
      return;
    }

    // Update playback state to new track
    const now = Date.now();
    const duration = nextTrack.metadata?.duration
      ? nextTrack.metadata.duration * 1000
      : 180000; // Default 3 minutes

    // Schedule next transition and get the job ID
    const nextJobId = await ctx.scheduler.runAfter(duration, internal.radioPlayback.advanceToNextTrack, {
      roomId: args.roomId,
    });

    await ctx.db.patch(args.roomId, {
      playbackState: {
        ...room.playbackState,
        currentTrackId: nextTrackId,
        isPlaying: true,
        position: 0,
        timestamp: now,
        queue: newQueue,
        queueIndex: 0, // Always 0 since we pop from front
        startedAt: now,
        trackDuration: duration,
        nextAdvanceJobId: nextJobId,
      },
    });

    // Check if queue needs refilling
    await ctx.scheduler.runAfter(
      0,
      internal.radioRooms.checkAndGenerateRadioSongs,
      { roomId: args.roomId }
    );
  },
});

// Check for track transitions (scheduled function)
export const checkRadioRoomTransitions = internalMutation({
  handler: async (ctx) => {
    // Query all radio rooms with active playback
    const allRooms = await ctx.db
      .query("rooms")
      .filter((q) => q.eq(q.field("type"), "radio"))
      .collect();

    const now = Date.now();

    for (const room of allRooms) {
      if (!room.playbackState?.isPlaying) continue;
      if (!room.playbackState.startedAt) continue;
      if (!room.playbackState.trackDuration) continue;

      const { startedAt, trackDuration } = room.playbackState;
      const elapsed = now - startedAt;

      // Check if track should have ended (with 1 second buffer)
      if (elapsed >= trackDuration - 1000) {
        // Advance to next track
        await ctx.scheduler.runAfter(0, internal.radioPlayback.advanceToNextTrack, {
          roomId: room._id,
        });
      }
    }
  },
});

// Manual skip to next track (for user controls)
export const skipToNextTrack = mutation({
  args: { roomId: v.id("rooms") },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Not authenticated");

    const room = await ctx.db.get(args.roomId);
    if (!room || room.type !== "radio") {
      throw new Error("Not a radio room");
    }

    // Check if user is a member
    const member = await ctx.db
      .query("spaceMembers")
      .withIndex("by_space_and_user", (q) =>
        q.eq("spaceId", room.spaceId).eq("userId", userId)
      )
      .first();

    if (!member || member.isKicked) {
      throw new Error("Not authorized");
    }

    // Advance to next track
    await ctx.scheduler.runAfter(0, internal.radioPlayback.advanceToNextTrack, {
      roomId: args.roomId,
    });
  },
});

// Start radio playback (for when queue is populated and room has users)
export const startRadioPlayback = internalMutation({
  args: { roomId: v.id("rooms") },
  handler: async (ctx, args) => {
    const room = await ctx.db.get(args.roomId);
    if (!room || room.type !== "radio") return;
    if (!room.playbackState) return;

    const { queue, currentTrackId, isPlaying } = room.playbackState;

    // Don't start if already playing
    if (isPlaying && currentTrackId) return;

    // Don't start if queue is empty
    if (queue.length === 0) return;

    // Check if room has users
    const hasUsers = await ctx.db
      .query("userPresence")
      .withIndex("by_room", (q) => q.eq("roomId", args.roomId))
      .first();

    if (!hasUsers) return;

    // Start playing first track in queue
    const firstTrackId = queue[0];
    const firstTrack = await ctx.db.get(firstTrackId);

    if (!firstTrack) return;

    // Wait if track is not ready
    if (
      firstTrack.status === "pending" ||
      firstTrack.status === "processing"
    ) {
      await ctx.scheduler.runAfter(5000, internal.radioPlayback.startRadioPlayback, {
        roomId: args.roomId,
      });
      return;
    }

    const now = Date.now();
    const duration = firstTrack.metadata?.duration
      ? firstTrack.metadata.duration * 1000
      : 180000;

    // Cancel any existing scheduled advance job
    if (room.playbackState.nextAdvanceJobId) {
      try {
        await ctx.scheduler.cancel(room.playbackState.nextAdvanceJobId);
      } catch (error) {
        console.log(`[startRadioPlayback] Could not cancel job ${room.playbackState.nextAdvanceJobId}`);
      }
    }

    // Schedule next track transition and get the job ID
    const nextJobId = await ctx.scheduler.runAfter(duration, internal.radioPlayback.advanceToNextTrack, {
      roomId: args.roomId,
    });

    await ctx.db.patch(args.roomId, {
      playbackState: {
        ...room.playbackState,
        currentTrackId: firstTrackId,
        isPlaying: true,
        position: 0,
        timestamp: now,
        queueIndex: 0,
        startedAt: now,
        trackDuration: duration,
        nextAdvanceJobId: nextJobId,
      },
    });
  },
});

// Update track duration when it becomes available from Suno
export const updateTrackDuration = internalMutation({
  args: {
    atomId: v.id("atoms"),
    durationSeconds: v.number(),
  },
  handler: async (ctx, args) => {
    // Find all radio rooms that have this atom as the current track
    const allRooms = await ctx.db
      .query("rooms")
      .filter((q) => q.eq(q.field("type"), "radio"))
      .collect();

    for (const room of allRooms) {
      if (!room.playbackState) continue;
      if (room.playbackState.currentTrackId !== args.atomId) continue;

      const { startedAt, nextAdvanceJobId } = room.playbackState;
      if (!startedAt) continue;

      const durationMs = args.durationSeconds * 1000;
      const elapsed = Date.now() - startedAt;
      const remaining = durationMs - elapsed;

      // Only update if there's still time remaining (not already finished)
      if (remaining > 1000) {
        // Cancel the old job
        if (nextAdvanceJobId) {
          try {
            await ctx.scheduler.cancel(nextAdvanceJobId);
          } catch (error) {
            console.log(`[updateTrackDuration] Could not cancel job ${nextAdvanceJobId}`);
          }
        }

        // Schedule new job with correct duration
        const newJobId = await ctx.scheduler.runAfter(
          remaining,
          internal.radioPlayback.advanceToNextTrack,
          { roomId: room._id }
        );

        // Update the track duration and job ID
        await ctx.db.patch(room._id, {
          playbackState: {
            ...room.playbackState,
            trackDuration: durationMs,
            nextAdvanceJobId: newJobId,
          },
        });

        console.log(
          `[updateTrackDuration] Updated room ${room._id}: duration=${args.durationSeconds}s, remaining=${Math.floor(remaining / 1000)}s`
        );
      }
    }
  },
});

// Update atom URL when it transitions from streaming to CDN
export const handleAtomUrlUpdate = internalMutation({
  args: {
    atomId: v.id("atoms"),
  },
  handler: async (ctx, args) => {
    // Find all radio rooms that have this atom in their queue or as current track
    const allRooms = await ctx.db
      .query("rooms")
      .filter((q) => q.eq(q.field("type"), "radio"))
      .collect();

    for (const room of allRooms) {
      if (!room.playbackState) continue;

      const { currentTrackId, queue } = room.playbackState;

      // Check if this atom is currently playing or in queue
      const isInQueue = queue.includes(args.atomId);
      const isCurrentTrack = currentTrackId === args.atomId;

      if (isInQueue || isCurrentTrack) {
        // The atom is relevant to this room
        // Clients will automatically pick up the updated URL via subscription
        // No action needed here, but we could log or notify if desired
        console.log(
          `Atom ${args.atomId} URL updated in room ${room._id}`
        );
      }
    }
  },
});
