"use node";

import { action } from "./_generated/server";
import { v } from "convex/values";
import { AccessToken, RoomServiceClient } from "livekit-server-sdk";

/**
 * Generate a LiveKit join token for a voice channel.
 *
 * LiveKit servers run with `room.auto_create: false` reject joins for
 * rooms that don't already exist — the client gets a 404 "requested
 * room does not exist" back from the /rtc/v1/validate endpoint. To
 * make this deployment-agnostic, we pre-create the room via the
 * LiveKit Server SDK before minting the token. When auto-create is
 * enabled the `createRoom` call is idempotent (409 Conflict is
 * swallowed silently), so the same code path works on both
 * configurations.
 *
 * Requires `LIVEKIT_URL` (or the frontend's `VITE_LIVEKIT_URL` as a
 * fallback) in the Convex environment so the RoomServiceClient
 * can talk to the LiveKit API.
 */
export const getToken = action({
  args: {
    channelId: v.string(),
    userId: v.string(),
    username: v.string(),
  },
  returns: v.object({ token: v.string() }),
  handler: async (_ctx, args) => {
    const apiKey = process.env.LIVEKIT_API_KEY || "devkey";
    const apiSecret = process.env.LIVEKIT_API_SECRET || "secret";
    const livekitUrl =
      process.env.LIVEKIT_URL || process.env.VITE_LIVEKIT_URL || "";

    // Ensure the room exists. The LiveKit API accepts `http(s)` URLs
    // for the management endpoint, but the frontend connect URL is a
    // `wss://` — swap the scheme when needed.
    if (livekitUrl) {
      const httpUrl = livekitUrl
        .replace(/^wss:\/\//i, "https://")
        .replace(/^ws:\/\//i, "http://");
      try {
        const roomService = new RoomServiceClient(httpUrl, apiKey, apiSecret);
        await roomService.createRoom({
          name: args.channelId,
          // Empty rooms auto-destroy after 5 minutes with no participants,
          // matching LiveKit's own default so stale rooms from a crashed
          // client don't pile up forever.
          emptyTimeout: 5 * 60,
          // 50 participants is plenty for a voice channel in this
          // single-server deployment and keeps any runaway join loop
          // from hitting the global limit.
          maxParticipants: 50,
        });
      } catch (err: any) {
        // 409 / "already exists" is expected when a room has already
        // been created by an earlier join — swallow it and continue.
        const message = String(err?.message ?? err ?? "");
        const status = err?.status ?? err?.statusCode;
        const alreadyExists =
          status === 409 ||
          /already exists/i.test(message) ||
          /AlreadyExists/i.test(message);
        if (!alreadyExists) {
          // Non-fatal: log and fall through to token generation. If the
          // real issue was misconfiguration the client will surface the
          // 404 it already does.
          console.warn("LiveKit createRoom failed:", message);
        }
      }
    }

    const at = new AccessToken(apiKey, apiSecret, {
      identity: args.userId,
      name: args.username,
      ttl: "24h",
    });

    at.addGrant({
      roomJoin: true,
      room: args.channelId,
      canPublish: true,
      canSubscribe: true,
      canPublishData: true,
    });

    const token = await at.toJwt();
    return { token };
  },
});
