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

// ========== RADIO ROOM MANAGEMENT ==========

// Create a new radio room
export const createRadioRoom = mutation({
  args: {
    spaceId: v.id("spaces"),
    name: v.string(),
    prompt: v.string(),
    atomReferences: v.optional(v.array(v.id("atoms"))),
  },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Not authenticated");

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

    if (!member || member.isKicked) {
      throw new Error("Not authorized - must be a space member");
    }

    // Create radio room
    const roomId = await ctx.db.insert("rooms", {
      spaceId: args.spaceId,
      name: args.name,
      type: "radio",
      radioConfig: {
        prompt: args.prompt,
        atomReferences: args.atomReferences,
        autoGenerate: true,
        queueTargetLength: 4,
      },
      playbackState: {
        currentTrackId: undefined,
        isPlaying: false,
        position: 0,
        timestamp: Date.now(),
        queue: [],
        queueIndex: 0,
        volume: 0.7,
        repeat: "none",
        shuffle: false,
        radioMode: "PROMPT",
        radioPrompt: args.prompt,
        startedAt: undefined,
        trackDuration: undefined,
        generatingNewSongs: false,
      },
    });

    // Trigger initial song generation
    await ctx.scheduler.runAfter(0, internal.radioRooms.generateRadioSongsAction, {
      roomId,
      prompt: args.prompt,
      atomReferences: args.atomReferences,
    });

    return roomId;
  },
});

// Update radio prompt
export const updateRadioPrompt = mutation({
  args: {
    roomId: v.id("rooms"),
    prompt: v.string(),
    atomReferences: v.optional(v.array(v.id("atoms"))),
  },
  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) throw new Error("Room not found");
    if (room.type !== "radio") throw new Error("Not a radio room");

    // Check if user is a member of the space
    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");
    }

    // Filter queue: keep songs that match the new prompt
    const currentQueue = room.playbackState?.queue ?? [];
    const filteredQueue: Id<"atoms">[] = [];

    for (const atomId of currentQueue) {
      const atom = await ctx.db.get(atomId);
      // Keep songs that were generated with the same prompt
      if (atom && atom.metadata?.radioPrompt === args.prompt) {
        filteredQueue.push(atomId);
      }
    }

    console.log(`[updateRadioPrompt] Filtered queue from ${currentQueue.length} to ${filteredQueue.length} songs`);

    // Update radio config and filtered queue (keep current track playing)
    await ctx.db.patch(args.roomId, {
      radioConfig: {
        ...room.radioConfig,
        prompt: args.prompt,
        atomReferences: args.atomReferences,
        autoGenerate: true,
        queueTargetLength: room.radioConfig?.queueTargetLength ?? 4,
      },
      playbackState: {
        ...room.playbackState,
        radioPrompt: args.prompt,
        generatingNewSongs: true,
        isPlaying: room.playbackState?.isPlaying ?? false,
        position: room.playbackState?.position ?? 0,
        timestamp: room.playbackState?.timestamp ?? Date.now(),
        queue: filteredQueue, // Keep songs that match the new prompt
        queueIndex: room.playbackState?.queueIndex ?? 0,
        volume: room.playbackState?.volume ?? 0.7,
        repeat: room.playbackState?.repeat ?? "none",
        shuffle: room.playbackState?.shuffle ?? false,
        radioMode: room.playbackState?.radioMode ?? "PROMPT",
      },
    });

    // Trigger generation for new prompt
    await ctx.scheduler.runAfter(0, internal.radioRooms.generateRadioSongsAction, {
      roomId: args.roomId,
      prompt: args.prompt,
      atomReferences: args.atomReferences,
    });
  },
});

// Get radio room state (includes populated queue atoms)
export const getRadioRoomState = query({
  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) return null;
    if (room.type !== "radio") return null;

    // Check if user is a member of the space
    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");
    }

    // Get queue atoms - fetch directly from DB
    const queueAtomIds = room.playbackState?.queue ?? [];
    const queueAtoms = await Promise.all(
      queueAtomIds.map((id) => ctx.db.get(id))
    );

    // Get current track
    const currentTrack = room.playbackState?.currentTrackId
      ? await ctx.db.get(room.playbackState.currentTrackId)
      : null;

    // Check if there's an active generation job
    const activeJob = await ctx.db
      .query("radioGenerationJobs")
      .withIndex("by_room_and_status", (q) =>
        q.eq("roomId", args.roomId).eq("status", "generating")
      )
      .first();

    return {
      room,
      currentTrack,
      queueAtoms: queueAtoms.filter((a) => a !== null),
      isGenerating: !!activeJob || room.playbackState?.generatingNewSongs,
    };
  },
});

// ========== QUEUE MANAGEMENT ==========

// Add generated songs to radio queue (internal)
export const addToRadioQueue = internalMutation({
  args: {
    roomId: v.id("rooms"),
    atomIds: v.array(v.id("atoms")),
  },
  handler: async (ctx, args) => {
    const room = await ctx.db.get(args.roomId);
    if (!room || room.type !== "radio") return;

    const currentState = room.playbackState;
    if (!currentState) return;

    const newQueue = [...currentState.queue, ...args.atomIds];

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

    // Case 1: Queue was empty and no current track - start playback immediately
    const shouldStartPlayback =
      currentState.queue.length === 0 &&
      !currentState.currentTrackId &&
      hasUsers;

    // Case 2: Queue was empty but there IS a current track - auto-advance to new song
    // (This happens when prompt was changed - old song still playing but queue cleared)
    const shouldAutoAdvance =
      currentState.queue.length === 0 &&
      currentState.currentTrackId &&
      hasUsers;

    if (shouldStartPlayback && args.atomIds.length > 0) {
      // Start playing the first song
      const firstAtom = await ctx.db.get(args.atomIds[0]);
      await ctx.db.patch(args.roomId, {
        playbackState: {
          ...currentState,
          currentTrackId: args.atomIds[0],
          isPlaying: true,
          position: 0,
          timestamp: Date.now(),
          queue: newQueue,
          queueIndex: 0,
          startedAt: Date.now(),
          trackDuration: firstAtom?.metadata?.duration
            ? firstAtom.metadata.duration * 1000
            : 180000, // Default 3 minutes
          generatingNewSongs: false,
        },
      });
    } else if (shouldAutoAdvance && args.atomIds.length > 0) {
      // Prompt changed - advance to first new song immediately
      await ctx.db.patch(args.roomId, {
        playbackState: {
          ...currentState,
          queue: newQueue,
          generatingNewSongs: false,
        },
      });
      // Trigger immediate advance to next track
      await ctx.scheduler.runAfter(0, internal.radioPlayback.advanceToNextTrack, {
        roomId: args.roomId,
      });
    } else {
      await ctx.db.patch(args.roomId, {
        playbackState: {
          ...currentState,
          queue: newQueue,
          generatingNewSongs: false,
        },
      });
    }
  },
});

// Check if queue needs refilling and trigger generation
export const checkAndGenerateRadioSongs = 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.radioConfig?.autoGenerate) return;

    const currentQueue = room.playbackState?.queue ?? [];
    const targetLength = room.radioConfig.queueTargetLength;

    // Check if we need more songs
    if (currentQueue.length >= targetLength) return;

    // Check if there's already an active generation job
    const activeJob = await ctx.db
      .query("radioGenerationJobs")
      .withIndex("by_room_and_status", (q) =>
        q.eq("roomId", args.roomId).eq("status", "generating")
      )
      .first();

    if (activeJob) return; // Already generating

    // Trigger generation
    await ctx.scheduler.runAfter(0, internal.radioRooms.generateRadioSongsAction, {
      roomId: args.roomId,
      prompt: room.radioConfig.prompt,
      atomReferences: room.radioConfig.atomReferences,
    });
  },
});

// ========== SONG GENERATION ==========

// Generate songs via Suno API (internal action)
export const generateRadioSongsAction = internalAction({
  args: {
    roomId: v.id("rooms"),
    prompt: v.string(),
    atomReferences: v.optional(v.array(v.id("atoms"))),
  },
  handler: async (ctx, args) => {
    // Get room details for spaceId and ownerId
    const room = await ctx.runQuery(internal.radioRooms.getRadioRoomInternal, {
      roomId: args.roomId,
    });

    if (!room) {
      throw new Error("Room not found");
    }

    // Create generation job
    const jobId = await ctx.runMutation(internal.radioRooms.createGenerationJob, {
      roomId: args.roomId,
      prompt: args.prompt,
    });

    try {
      // Mark job as generating
      await ctx.runMutation(internal.radioRooms.updateGenerationJobStatus, {
        jobId,
        status: "generating",
      });

      // Use OpenAI assistant to parse the radio prompt and determine generation parameters
      // This handles atom references and extracts lyrics, tags, etc.
      const result = await ctx.runAction(internal.radioRooms.parseRadioPromptWithAI, {
        prompt: args.prompt,
        atomReferences: args.atomReferences,
        spaceId: room.spaceId,
        userId: room.ownerId,
      });

      if (!result) {
        throw new Error("Failed to parse radio prompt");
      }

      // Generate songs using the parsed parameters
      const atomIds = await ctx.runAction(internal.atoms.generateSong, {
        lyrics: result.lyrics,
        tags: result.tags,
        makeInstrumental: result.makeInstrumental,
        coverClipId: result.coverClipId,
        spaceId: result.spaceId,
        userId: result.userId,
        radioPrompt: args.prompt, // Store original radio prompt
      });

      if (!atomIds || atomIds.length === 0) {
        throw new Error("Song generation returned no atoms");
      }

      // Update job with generated atom IDs
      await ctx.runMutation(internal.radioRooms.updateGenerationJobStatus, {
        jobId,
        status: "completed",
        atomIds: atomIds,
      });

      // Add songs to queue
      await ctx.runMutation(internal.radioRooms.addToRadioQueue, {
        roomId: args.roomId,
        atomIds: atomIds,
      });
    } catch (error) {
      console.error("Radio generation failed:", error);
      await ctx.runMutation(internal.radioRooms.updateGenerationJobStatus, {
        jobId,
        status: "failed",
        error: error instanceof Error ? error.message : "Unknown error",
      });
    }
  },
});

// Get radio room internal (for actions)
export const getRadioRoomInternal = internalQuery({
  args: { roomId: v.id("rooms") },
  handler: async (ctx, args) => {
    const room = await ctx.db.get(args.roomId);
    if (!room || room.type !== "radio") return null;

    // Get space to find owner
    const space = await ctx.db.get(room.spaceId);
    if (!space) return null;

    return {
      spaceId: room.spaceId,
      ownerId: space.ownerId,
      prompt: room.radioConfig?.prompt,
      atomReferences: room.radioConfig?.atomReferences,
    };
  },
});

// Parse radio prompt using OpenAI assistant (internal action)
export const parseRadioPromptWithAI = internalAction({
  args: {
    prompt: v.string(),
    atomReferences: v.optional(v.array(v.id("atoms"))),
    spaceId: v.id("spaces"),
    userId: v.id("users"),
  },
  handler: async (ctx, args): Promise<{
    lyrics: string;
    tags: string | undefined;
    makeInstrumental: boolean;
    coverClipId: string | undefined;
    spaceId: Id<"spaces">;
    userId: Id<"users">;
  }> => {
    // Use the assistant's classifyIntent to parse the radio prompt
    // This will generate proper lyrics and extract tags
    const result = await ctx.runAction(internal.assistant.classifyIntent, {
      messageContent: args.prompt,
      atomReferences: args.atomReferences,
    });

    if (!result || result.toolName !== "generate_song") {
      // If OpenAI doesn't return a generate_song tool, use the prompt directly
      console.log("[RadioRoom] OpenAI didn't classify as generate_song, using prompt directly");
      return {
        lyrics: args.prompt,
        tags: undefined,
        makeInstrumental: false,
        coverClipId: undefined,
        spaceId: args.spaceId,
        userId: args.userId,
      };
    }

    const params = result.params as {
      lyrics: string;
      tags?: string;
      makeInstrumental?: boolean;
      coverAtomId?: string;
    };

    // Get cover clip ID if atom reference is provided
    let coverClipId: string | undefined;
    let originalPrompt = params.lyrics || args.prompt;
    if (params.coverAtomId && args.atomReferences && args.atomReferences.length > 0) {
      try {
        const coverAtom: any = await ctx.runQuery(internal.atoms.getAtomInternal, {
          id: args.atomReferences[0],
        });
        if (coverAtom && coverAtom.type === "song" && coverAtom.metadata?.sunoClipId) {
          coverClipId = coverAtom.metadata.sunoClipId;
          // Use original song's lyrics/prompt for cover
          if (coverAtom.metadata?.prompt) {
            originalPrompt = coverAtom.metadata.prompt;
          }
        }
      } catch (error) {
        console.error("Failed to fetch cover atom:", error);
      }
    }

    console.log("[RadioRoom] OpenAI parsed prompt:", {
      lyrics: originalPrompt ? originalPrompt.substring(0, 100) + "..." : "(empty)",
      tags: params.tags,
      makeInstrumental: params.makeInstrumental,
    });

    return {
      lyrics: originalPrompt,
      tags: params.tags,
      makeInstrumental: params.makeInstrumental || false,
      coverClipId,
      spaceId: args.spaceId,
      userId: args.userId,
    };
  },
});

// Create generation job (internal)
export const createGenerationJob = internalMutation({
  args: {
    roomId: v.id("rooms"),
    prompt: v.string(),
  },
  handler: async (ctx, args) => {
    return await ctx.db.insert("radioGenerationJobs", {
      roomId: args.roomId,
      prompt: args.prompt,
      status: "pending",
      createdAt: Date.now(),
    });
  },
});

// Update generation job status (internal)
export const updateGenerationJobStatus = internalMutation({
  args: {
    jobId: v.id("radioGenerationJobs"),
    status: v.union(
      v.literal("pending"),
      v.literal("generating"),
      v.literal("completed"),
      v.literal("failed")
    ),
    atomIds: v.optional(v.array(v.id("atoms"))),
    error: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const updates: any = {
      status: args.status,
    };

    if (args.status === "completed" || args.status === "failed") {
      updates.completedAt = Date.now();
    }

    if (args.atomIds) {
      updates.generatedAtomIds = args.atomIds;
    }

    if (args.error) {
      updates.error = args.error;
    }

    await ctx.db.patch(args.jobId, updates);
  },
});

// ========== PRESENCE INTEGRATION ==========

// Handle user entering/leaving radio room
export const updateRadioRoomPresence = internalMutation({
  args: {
    roomId: v.id("rooms"),
    isEntering: v.boolean(),
    userId: v.id("users"),
  },
  handler: async (ctx, args) => {
    const userId = args.userId;

    const room = await ctx.db.get(args.roomId);
    if (!room || room.type !== "radio") return;

    // Count users in room
    const usersInRoom = await ctx.db
      .query("userPresence")
      .withIndex("by_room", (q) => q.eq("roomId", args.roomId))
      .collect();

    const activeUsers = usersInRoom.filter((p) => p.status === "online");

    if (args.isEntering) {
      // User entering - resume or start playback if room was empty
      if (activeUsers.length === 0 && room.playbackState) {
        const hasQueue = room.playbackState.queue.length > 0;
        const hasCurrentTrack = !!room.playbackState.currentTrackId;

        if (hasQueue && !hasCurrentTrack) {
          // Queue has songs but no current track - start playing first song
          await ctx.scheduler.runAfter(
            0,
            internal.radioPlayback.startRadioPlayback,
            { roomId: args.roomId }
          );
        } else if (hasQueue) {
          // Resume playback
          await ctx.db.patch(args.roomId, {
            playbackState: {
              ...room.playbackState,
              isPlaying: true,
              timestamp: Date.now(),
            },
          });
        }

        // Check if queue needs refilling
        await ctx.scheduler.runAfter(
          0,
          internal.radioRooms.checkAndGenerateRadioSongs,
          { roomId: args.roomId }
        );
      }
    } else {
      // User leaving - pause if room becomes empty
      if (activeUsers.length === 1 && activeUsers[0].userId === userId) {
        // This is the last user leaving
        if (room.playbackState) {
          await ctx.db.patch(args.roomId, {
            playbackState: {
              ...room.playbackState,
              isPlaying: false,
              timestamp: Date.now(),
            },
          });
        }

        // Cancel any pending generation jobs
        const pendingJobs = await ctx.db
          .query("radioGenerationJobs")
          .withIndex("by_room_and_status", (q) =>
            q.eq("roomId", args.roomId).eq("status", "generating")
          )
          .collect();

        for (const job of pendingJobs) {
          await ctx.db.patch(job._id, {
            status: "failed",
            error: "Room became empty",
            completedAt: Date.now(),
          });
        }
      }
    }
  },
});

// Clean up old generation jobs (scheduled)
export const cleanupOldJobs = internalMutation({
  handler: async (ctx) => {
    const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;

    // Find old completed/failed jobs
    const oldJobs = await ctx.db
      .query("radioGenerationJobs")
      .filter((q) =>
        q.and(
          q.or(
            q.eq(q.field("status"), "completed"),
            q.eq(q.field("status"), "failed")
          ),
          q.lt(q.field("createdAt"), oneDayAgo)
        )
      )
      .collect();

    // Delete old jobs
    await Promise.all(oldJobs.map((job) => ctx.db.delete(job._id)));

    return { deleted: oldJobs.length };
  },
});
