import { TrackSource } from '@livekit/protocol';
import { AccessToken, RoomServiceClient, type ParticipantInfo } from 'livekit-server-sdk';
import { getLiveKitConfig, getLiveKitHttpServerHost } from '../config/livekit';
import { withTimeout } from '../utils/asyncTimeout';

const LIVEKIT_ROOM_TIMEOUT_MS = Number(process.env.LIVEKIT_ROOM_TIMEOUT_MS || 12_000);
const LIVEKIT_ROOM_CACHE_TTL_MS = Number(process.env.LIVEKIT_ROOM_CACHE_TTL_MS || 30 * 60_000);

/** ห้องที่ ensure แล้ว — ลดการเรียก LiveKit API ซ้ำทุก token request */
const ensuredRoomExpiresAt = new Map<string, number>();

export type LiveKitParticipantRole = 'student' | 'proctor';

export function buildExamRoomName(courseId: string, recid: string, itemid: string): string {
  const slug = [courseId, recid, itemid]
    .map((part) =>
      String(part)
        .trim()
        .replace(/[^a-zA-Z0-9-_]/g, '-')
        .replace(/-+/g, '-')
        .replace(/^-|-$/g, ''),
    )
    .filter(Boolean)
    .join('-');
  return `exam-${slug || 'room'}`.slice(0, 120);
}

export function buildParticipantIdentity(
  role: LiveKitParticipantRole,
  userId: string,
): string {
  const safeId = String(userId).replace(/[^a-zA-Z0-9-_]/g, '_').slice(0, 80);
  return `${role}-${safeId}`;
}

export async function createLiveKitAccessToken(options: {
  roomName: string;
  identity: string;
  role: LiveKitParticipantRole;
  name?: string;
  ttlSeconds?: number;
}): Promise<string> {
  const { apiKey, apiSecret } = getLiveKitConfig();
  const { roomName, identity, name, ttlSeconds = 6 * 60 * 60 } = options;

  const token = new AccessToken(apiKey, apiSecret, {
    identity,
    name: name || identity,
    ttl: ttlSeconds,
  });

  token.addGrant({
    roomJoin: true,
    room: roomName,
    canPublish: true,
    canSubscribe: true,
    canPublishData: true,
    canUpdateOwnMetadata: true,
    canPublishSources: [
      TrackSource.CAMERA,
      TrackSource.MICROPHONE,
      TrackSource.SCREEN_SHARE,
      TrackSource.SCREEN_SHARE_AUDIO,
    ],
  });

  return token.toJwt();
}

function isRoomEnsuredRecently(roomName: string): boolean {
  const expiresAt = ensuredRoomExpiresAt.get(roomName);
  if (!expiresAt) return false;
  if (expiresAt <= Date.now()) {
    ensuredRoomExpiresAt.delete(roomName);
    return false;
  }
  return true;
}

/** สร้างห้องบน LiveKit Server (ถ้ายังไม่มี) — มี timeout + cache */
export async function ensureLiveKitRoom(roomName: string): Promise<void> {
  if (isRoomEnsuredRecently(roomName)) return;

  const { apiKey, apiSecret } = getLiveKitConfig();
  const host = getLiveKitHttpServerHost();
  const client = new RoomServiceClient(host, apiKey, apiSecret);

  try {
    const minPlayoutDelay = Number(process.env.LIVEKIT_MIN_PLAYOUT_DELAY_MS || 0);
    const maxPlayoutDelay = Number(process.env.LIVEKIT_MAX_PLAYOUT_DELAY_MS || 300);
    await withTimeout(
      client.createRoom({
        name: roomName,
        emptyTimeout: 60 * 60,
        departureTimeout: 5 * 60,
        minPlayoutDelay,
        maxPlayoutDelay,
      }),
      LIVEKIT_ROOM_TIMEOUT_MS,
      `LiveKit createRoom timeout (${LIVEKIT_ROOM_TIMEOUT_MS}ms) — ตรวจ LIVEKIT_SERVER_URL`,
    );
    ensuredRoomExpiresAt.set(roomName, Date.now() + LIVEKIT_ROOM_CACHE_TTL_MS);
  } catch (err: unknown) {
    const message = err instanceof Error ? err.message : String(err);
    if (/already exists/i.test(message)) {
      ensuredRoomExpiresAt.set(roomName, Date.now() + LIVEKIT_ROOM_CACHE_TTL_MS);
      return;
    }
    throw err;
  }
}

/** ล้าง cache เมื่อสร้างห้องใหม่ชื่อเดิม */
export function invalidateLiveKitRoomCache(roomName: string): void {
  ensuredRoomExpiresAt.delete(roomName);
}

function createRoomServiceClient(): RoomServiceClient {
  const { apiKey, apiSecret } = getLiveKitConfig();
  const host = getLiveKitHttpServerHost();
  return new RoomServiceClient(host, apiKey, apiSecret);
}

function findMicrophoneTrackSid(participant: ParticipantInfo): string | undefined {
  const tracks = participant.tracks ?? [];
  return tracks.find((t) => t.source === TrackSource.MICROPHONE)?.sid;
}

/** ปิดไมค์ผู้สอบรายคนที่ LiveKit server (บังคับ mute track) */
export async function muteStudentMicrophone(roomName: string, userId: string): Promise<boolean> {
  const identity = buildParticipantIdentity('student', userId);
  const client = createRoomServiceClient();
  const participants = await withTimeout(
    client.listParticipants(roomName),
    LIVEKIT_ROOM_TIMEOUT_MS,
    'LiveKit listParticipants timeout',
  );
  const participant = participants.find((p) => p.identity === identity);
  if (!participant) return false;

  const trackSid = findMicrophoneTrackSid(participant);
  if (!trackSid) return false;

  await withTimeout(
    client.mutePublishedTrack(roomName, identity, trackSid, true),
    LIVEKIT_ROOM_TIMEOUT_MS,
    'LiveKit mutePublishedTrack timeout',
  );
  return true;
}

/** ปิดไมค์ผู้สอบทุกคนในห้อง LiveKit */
export async function muteAllStudentMicrophones(roomName: string): Promise<number> {
  const client = createRoomServiceClient();
  const participants = await withTimeout(
    client.listParticipants(roomName),
    LIVEKIT_ROOM_TIMEOUT_MS,
    'LiveKit listParticipants timeout',
  );

  let muted = 0;
  for (const participant of participants) {
    if (!participant.identity?.startsWith('student-')) continue;
    const trackSid = findMicrophoneTrackSid(participant);
    if (!trackSid) continue;
    try {
      await client.mutePublishedTrack(roomName, participant.identity, trackSid, true);
      muted += 1;
    } catch (err) {
      console.warn(`mutePublishedTrack failed for ${participant.identity}:`, err);
    }
  }
  return muted;
}
