import { prisma } from '../../config/database';
import { TicketedEventStatus, TicketedEventVisibility, EventFormat, Prisma } from '@prisma/client';
import { AppError } from '../../middleware/error-handler';
import { unifiedCalendarService } from '../events/unified-calendar.service';
import { siteSettingsService } from '../settings/settings.service';
import { generateModeratorToken } from '../jitsi/jitsi.utils';
import { generateSlug as generateMeetingSlug } from '../../utils/slug';
import { env } from '../../config/env';
import crypto from 'crypto';
import { EVENTS_ROLES } from '../../utils/roles';
import { eventBus } from '../../services/event-bus.service';

function generateSlug(title: string): string {
  return title
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-|-$/g, '')
    .slice(0, 80);
}

async function uniqueSlug(base: string): Promise<string> {
  let slug = base;
  let suffix = 0;
  while (await prisma.ticketedEvent.findUnique({ where: { slug } })) {
    suffix++;
    slug = `${base}-${suffix}`;
  }
  return slug;
}

function generateInviteCode(): string {
  return crypto.randomBytes(6).toString('hex').toUpperCase();
}

// Use EVENTS_ROLES for ownership bypass checks (imported from utils/roles)
// Previously hardcoded as ['SUPER_ADMIN', 'INFLUENCE_ADMIN', 'MAP_ADMIN'] which
// missed EVENTS_ADMIN, causing a privilege inversion
const ADMIN_ROLES = EVENTS_ROLES.map(r => r.toString());

/** Validate that enableMeet is on when format requires Jitsi */
async function validateMeetFormat(format: EventFormat | string) {
  if (format === 'IN_PERSON') return;
  const settings = await siteSettingsService.get();
  if (!settings.enableMeet) {
    throw new AppError(400, 'Jitsi Meet must be enabled in Settings to create ONLINE or HYBRID events', 'MEET_DISABLED');
  }
}

/** Create a Meeting record for a ticketed event */
async function ensureMeeting(event: { id: string; title: string; date: Date; startTime: string; endTime: string }, userId: string): Promise<string> {
  const jitsiRoom = crypto.randomUUID();
  const slug = generateMeetingSlug(event.title);

  // Compose start/end DateTimes from event date + time strings
  const dateStr = event.date.toISOString().split('T')[0];
  const startTime = new Date(`${dateStr}T${event.startTime}:00`);
  const endTime = new Date(`${dateStr}T${event.endTime}:00`);

  const meeting = await prisma.meeting.create({
    data: {
      slug,
      title: `${event.title} — Live`,
      jitsiRoom,
      isActive: true,
      createdByUserId: userId,
      startTime,
      endTime,
    },
  });

  return meeting.id;
}

export const ticketedEventsService = {
  async create(data: {
    title: string;
    description?: string;
    richDescription?: string;
    date: string;
    startTime: string;
    endTime: string;
    doorsOpenTime?: string;
    eventFormat?: EventFormat;
    venueName?: string;
    venueAddress?: string;
    latitude?: number;
    longitude?: number;
    visibility?: TicketedEventVisibility;
    coverImageUrl?: string;
    maxAttendees?: number;
    organizerName?: string;
    organizerEmail?: string;
    tiers?: Array<{
      name: string;
      description?: string;
      tierType: 'PAID' | 'FREE' | 'DONATION';
      priceCAD?: number;
      minDonationCAD?: number;
      maxQuantity?: number;
      maxPerOrder?: number;
      salesStartAt?: string;
      salesEndAt?: string;
      sortOrder?: number;
    }>;
  }, userId: string) {
    const eventFormat = data.eventFormat || 'IN_PERSON';
    await validateMeetFormat(eventFormat);

    const slug = await uniqueSlug(generateSlug(data.title));
    const visibility = data.visibility || 'PUBLIC';
    const inviteCode = visibility === 'PRIVATE' ? generateInviteCode() : null;

    const event = await prisma.ticketedEvent.create({
      data: {
        slug,
        title: data.title,
        description: data.description,
        richDescription: data.richDescription,
        date: new Date(data.date),
        startTime: data.startTime,
        endTime: data.endTime,
        doorsOpenTime: data.doorsOpenTime,
        eventFormat,
        venueName: data.venueName,
        venueAddress: data.venueAddress,
        latitude: data.latitude,
        longitude: data.longitude,
        status: 'DRAFT',
        visibility,
        inviteCode,
        coverImageUrl: data.coverImageUrl,
        maxAttendees: data.maxAttendees,
        createdByUserId: userId,
        organizerName: data.organizerName,
        organizerEmail: data.organizerEmail,
        ticketTiers: data.tiers?.length ? {
          create: data.tiers.map((t, i) => ({
            name: t.name,
            description: t.description,
            tierType: t.tierType,
            priceCAD: t.priceCAD || 0,
            minDonationCAD: t.minDonationCAD,
            maxQuantity: t.maxQuantity,
            maxPerOrder: t.maxPerOrder || 10,
            salesStartAt: t.salesStartAt ? new Date(t.salesStartAt) : undefined,
            salesEndAt: t.salesEndAt ? new Date(t.salesEndAt) : undefined,
            sortOrder: t.sortOrder ?? i,
          })),
        } : undefined,
      },
      include: { ticketTiers: true },
    });

    // Auto-create meeting for ONLINE/HYBRID events
    if (eventFormat !== 'IN_PERSON') {
      const meetingId = await ensureMeeting(
        { id: event.id, title: event.title, date: event.date, startTime: event.startTime, endTime: event.endTime },
        userId,
      );
      return prisma.ticketedEvent.update({
        where: { id: event.id },
        data: { meetingId },
        include: { ticketTiers: true, meeting: { select: { jitsiRoom: true, isActive: true, slug: true } } },
      });
    }

    return event;
  },

  async update(id: string, data: Record<string, unknown>, userId: string, userRole: string) {
    const event = await prisma.ticketedEvent.findUnique({ where: { id } });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');

    // Non-admin users can only edit their own events
    if (!ADMIN_ROLES.includes(userRole) && event.createdByUserId !== userId) {
      throw new AppError(403, 'Cannot edit events you did not create', 'FORBIDDEN');
    }

    // If switching to PRIVATE, generate invite code if not present
    if (data.visibility === 'PRIVATE' && !event.inviteCode) {
      data.inviteCode = generateInviteCode();
    }

    // Convert date string to Date object
    if (typeof data.date === 'string') {
      data.date = new Date(data.date as string);
    }

    // Handle event format changes
    const newFormat = data.eventFormat as EventFormat | undefined;
    if (newFormat) {
      await validateMeetFormat(newFormat);

      // Switching TO ONLINE/HYBRID — create meeting if not present
      if (newFormat !== 'IN_PERSON' && !event.meetingId) {
        const meetingId = await ensureMeeting(
          { id: event.id, title: event.title, date: event.date, startTime: event.startTime, endTime: event.endTime },
          userId,
        );
        data.meetingId = meetingId;
      }

      // Switching TO IN_PERSON — deactivate meeting (keep the record)
      if (newFormat === 'IN_PERSON' && event.meetingId) {
        await prisma.meeting.update({
          where: { id: event.meetingId },
          data: { isActive: false },
        });
      }

      // Re-activate meeting if switching back to ONLINE/HYBRID
      if (newFormat !== 'IN_PERSON' && event.meetingId) {
        await prisma.meeting.update({
          where: { id: event.meetingId },
          data: { isActive: true },
        });
      }
    }

    const updated = await prisma.ticketedEvent.update({
      where: { id },
      data: data as Prisma.TicketedEventUncheckedUpdateInput,
      include: {
        ticketTiers: { orderBy: { sortOrder: 'asc' } },
        meeting: { select: { jitsiRoom: true, isActive: true, slug: true } },
      },
    });

    // Bust calendar cache if event is published
    if (updated.status === 'PUBLISHED') {
      unifiedCalendarService.bustCache().catch(() => {});
    }

    return updated;
  },

  async findById(id: string) {
    const event = await prisma.ticketedEvent.findUnique({
      where: { id },
      include: {
        ticketTiers: { orderBy: { sortOrder: 'asc' } },
        createdBy: { select: { id: true, name: true, email: true } },
        meeting: { select: { id: true, jitsiRoom: true, isActive: true, slug: true } },
        _count: { select: { tickets: true, checkIns: true } },
      },
    });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');
    return event;
  },

  async findBySlug(slug: string) {
    const event = await prisma.ticketedEvent.findUnique({
      where: { slug },
      include: {
        ticketTiers: {
          where: { isActive: true },
          orderBy: { sortOrder: 'asc' },
        },
        createdBy: { select: { id: true, name: true } },
        meeting: { select: { isActive: true } },
      },
    });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');
    return event;
  },

  async list(filters: {
    page: number;
    limit: number;
    status?: TicketedEventStatus;
    search?: string;
    createdByUserId?: string;
  }) {
    const where: Prisma.TicketedEventWhereInput = {};
    if (filters.status) where.status = filters.status;
    if (filters.createdByUserId) where.createdByUserId = filters.createdByUserId;
    if (filters.search) {
      where.OR = [
        { title: { contains: filters.search, mode: 'insensitive' } },
        { venueName: { contains: filters.search, mode: 'insensitive' } },
      ];
    }

    const [events, total] = await Promise.all([
      prisma.ticketedEvent.findMany({
        where,
        skip: (filters.page - 1) * filters.limit,
        take: filters.limit,
        orderBy: { date: 'desc' },
        include: {
          ticketTiers: { orderBy: { sortOrder: 'asc' } },
          _count: { select: { tickets: true, checkIns: true } },
        },
      }),
      prisma.ticketedEvent.count({ where }),
    ]);

    return {
      events,
      pagination: {
        page: filters.page,
        limit: filters.limit,
        total,
        totalPages: Math.ceil(total / filters.limit),
      },
    };
  },

  async listPublished(filters: {
    page: number;
    limit: number;
    upcoming?: boolean;
  }) {
    const where: Prisma.TicketedEventWhereInput = {
      status: 'PUBLISHED',
      visibility: { in: ['PUBLIC', 'UNLISTED'] },
    };
    if (filters.upcoming) {
      where.date = { gte: new Date() };
    }

    const [events, total] = await Promise.all([
      prisma.ticketedEvent.findMany({
        where,
        skip: (filters.page - 1) * filters.limit,
        take: filters.limit,
        orderBy: { date: 'asc' },
        include: {
          ticketTiers: {
            where: { isActive: true },
            orderBy: { sortOrder: 'asc' },
          },
        },
      }),
      prisma.ticketedEvent.count({ where }),
    ]);

    return {
      events,
      pagination: {
        page: filters.page,
        limit: filters.limit,
        total,
        totalPages: Math.ceil(total / filters.limit),
      },
    };
  },

  async publish(id: string, userId: string, userRole: string) {
    const event = await prisma.ticketedEvent.findUnique({
      where: { id },
      include: { ticketTiers: true },
    });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');

    if (!ADMIN_ROLES.includes(userRole) && event.createdByUserId !== userId) {
      throw new AppError(403, 'Cannot publish events you did not create', 'FORBIDDEN');
    }

    if (event.status !== 'DRAFT' && event.status !== 'PENDING_APPROVAL') {
      throw new AppError(400, `Cannot publish event in ${event.status} status`, 'INVALID_STATUS');
    }

    if (!event.ticketTiers.length) {
      throw new AppError(400, 'Event must have at least one ticket tier before publishing', 'NO_TIERS');
    }

    // Check if approval is required for non-admin users
    if (!ADMIN_ROLES.includes(userRole)) {
      const settings = await prisma.siteSettings.findFirst();
      if (settings?.requireEventApproval) {
        const updated = await prisma.ticketedEvent.update({
          where: { id },
          data: { status: 'PENDING_APPROVAL' },
          include: { ticketTiers: true },
        });
        return updated;
      }
    }

    const updated = await prisma.ticketedEvent.update({
      where: { id },
      data: { status: 'PUBLISHED' },
      include: { ticketTiers: true },
    });

    // Calendar cache bust (fire-and-forget)
    unifiedCalendarService.bustCache().catch(() => {});

    eventBus.publish('ticketed-event.published', {
      eventId: updated.id,
      title: updated.title,
      date: updated.date.toISOString().split('T')[0],
      startTime: updated.startTime,
      endTime: updated.endTime,
      location: updated.venueAddress || updated.venueName || undefined,
      gancioEventId: updated.gancioEventId ?? undefined,
    });

    return updated;
  },

  async approve(id: string) {
    const event = await prisma.ticketedEvent.findUnique({ where: { id } });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');
    if (event.status !== 'PENDING_APPROVAL') {
      throw new AppError(400, 'Event is not pending approval', 'INVALID_STATUS');
    }

    const updated = await prisma.ticketedEvent.update({
      where: { id },
      data: { status: 'PUBLISHED' },
      include: { ticketTiers: true },
    });

    unifiedCalendarService.bustCache().catch(() => {});

    eventBus.publish('ticketed-event.published', {
      eventId: updated.id,
      title: updated.title,
      date: updated.date.toISOString().split('T')[0],
      startTime: updated.startTime,
      endTime: updated.endTime,
      location: updated.venueAddress || updated.venueName || undefined,
      gancioEventId: updated.gancioEventId ?? undefined,
    });

    return updated;
  },

  async reject(id: string) {
    const event = await prisma.ticketedEvent.findUnique({ where: { id } });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');
    if (event.status !== 'PENDING_APPROVAL') {
      throw new AppError(400, 'Event is not pending approval', 'INVALID_STATUS');
    }

    return prisma.ticketedEvent.update({
      where: { id },
      data: { status: 'DRAFT' },
    });
  },

  async cancel(id: string, userId: string, userRole: string) {
    const event = await prisma.ticketedEvent.findUnique({ where: { id } });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');

    if (!ADMIN_ROLES.includes(userRole) && event.createdByUserId !== userId) {
      throw new AppError(403, 'Cannot cancel events you did not create', 'FORBIDDEN');
    }

    if (event.status === 'CANCELLED' || event.status === 'COMPLETED') {
      throw new AppError(400, `Cannot cancel event in ${event.status} status`, 'INVALID_STATUS');
    }

    // Cancel all valid tickets
    await prisma.ticket.updateMany({
      where: { eventId: id, status: 'VALID' },
      data: { status: 'CANCELLED' },
    });

    const updated = await prisma.ticketedEvent.update({
      where: { id },
      data: { status: 'CANCELLED' },
    });

    // Calendar cache bust (fire-and-forget)
    unifiedCalendarService.bustCache().catch(() => {});

    eventBus.publish('ticketed-event.cancelled', {
      eventId: updated.id,
      title: event.title,
    });

    return updated;
  },

  async complete(id: string) {
    const event = await prisma.ticketedEvent.findUnique({ where: { id } });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');
    if (event.status !== 'PUBLISHED') {
      throw new AppError(400, 'Only published events can be completed', 'INVALID_STATUS');
    }

    const updated = await prisma.ticketedEvent.update({
      where: { id },
      data: { status: 'COMPLETED' },
    });
    unifiedCalendarService.bustCache().catch(() => {});
    return updated;
  },

  async deleteEvent(id: string, userId: string, userRole: string) {
    const event = await prisma.ticketedEvent.findUnique({
      where: { id },
      include: { _count: { select: { tickets: true } } },
    });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');

    if (!ADMIN_ROLES.includes(userRole) && event.createdByUserId !== userId) {
      throw new AppError(403, 'Cannot delete events you did not create', 'FORBIDDEN');
    }

    if (event.status !== 'DRAFT') {
      throw new AppError(400, 'Only draft events can be deleted. Cancel the event instead.', 'INVALID_STATUS');
    }

    if (event.gancioEventId) {
      eventBus.publish('ticketed-event.cancelled', {
        eventId: event.id,
        title: event.title,
      });
    }

    await prisma.ticketedEvent.delete({ where: { id } });
  },

  // --- Tier Management ---

  async addTier(eventId: string, data: {
    name: string;
    description?: string;
    tierType: 'PAID' | 'FREE' | 'DONATION';
    priceCAD?: number;
    minDonationCAD?: number;
    maxQuantity?: number;
    maxPerOrder?: number;
    salesStartAt?: string;
    salesEndAt?: string;
    sortOrder?: number;
  }) {
    const event = await prisma.ticketedEvent.findUnique({ where: { id: eventId } });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');

    return prisma.ticketTier.create({
      data: {
        eventId,
        name: data.name,
        description: data.description,
        tierType: data.tierType,
        priceCAD: data.priceCAD || 0,
        minDonationCAD: data.minDonationCAD,
        maxQuantity: data.maxQuantity,
        maxPerOrder: data.maxPerOrder || 10,
        salesStartAt: data.salesStartAt ? new Date(data.salesStartAt) : undefined,
        salesEndAt: data.salesEndAt ? new Date(data.salesEndAt) : undefined,
        sortOrder: data.sortOrder ?? 0,
      },
    });
  },

  async updateTier(tierId: string, data: Record<string, unknown>, eventId?: string) {
    const tier = await prisma.ticketTier.findUnique({ where: { id: tierId } });
    if (!tier) throw new AppError(404, 'Tier not found', 'NOT_FOUND');

    // Verify tier belongs to the specified event (prevents IDOR)
    if (eventId && tier.eventId !== eventId) {
      throw new AppError(403, 'Tier does not belong to this event', 'FORBIDDEN');
    }

    // Convert date strings
    if (typeof data.salesStartAt === 'string') data.salesStartAt = new Date(data.salesStartAt as string);
    if (typeof data.salesEndAt === 'string') data.salesEndAt = new Date(data.salesEndAt as string);

    return prisma.ticketTier.update({
      where: { id: tierId },
      data: data as Prisma.TicketTierUncheckedUpdateInput,
    });
  },

  async deleteTier(tierId: string, eventId?: string) {
    const tier = await prisma.ticketTier.findUnique({
      where: { id: tierId },
      include: { _count: { select: { tickets: true } } },
    });
    if (!tier) throw new AppError(404, 'Tier not found', 'NOT_FOUND');

    // Verify tier belongs to the specified event (prevents IDOR)
    if (eventId && tier.eventId !== eventId) {
      throw new AppError(403, 'Tier does not belong to this event', 'FORBIDDEN');
    }

    if (tier._count.tickets > 0) {
      throw new AppError(400, 'Cannot delete a tier that has sold tickets', 'HAS_TICKETS');
    }

    await prisma.ticketTier.delete({ where: { id: tierId } });
  },

  // --- Stats ---

  async getEventStats(eventId: string) {
    const [ticketCounts, revenue, checkInCount, tierStats] = await Promise.all([
      prisma.ticket.groupBy({
        by: ['status'],
        where: { eventId },
        _count: true,
      }),
      prisma.order.aggregate({
        where: {
          tickets: { some: { eventId } },
          status: 'COMPLETED',
          type: 'event_ticket',
        },
        _sum: { amountCAD: true },
      }),
      prisma.checkIn.count({ where: { eventId } }),
      prisma.ticketTier.findMany({
        where: { eventId },
        orderBy: { sortOrder: 'asc' },
        select: {
          id: true,
          name: true,
          tierType: true,
          priceCAD: true,
          maxQuantity: true,
          soldCount: true,
          isActive: true,
        },
      }),
    ]);

    const statusMap: Record<string, number> = {};
    for (const g of ticketCounts) statusMap[g.status] = g._count;

    return {
      totalTickets: Object.values(statusMap).reduce((a, b) => a + b, 0),
      validTickets: statusMap['VALID'] || 0,
      checkedIn: statusMap['CHECKED_IN'] || 0,
      cancelled: statusMap['CANCELLED'] || 0,
      refunded: statusMap['REFUNDED'] || 0,
      totalRevenue: revenue._sum.amountCAD || 0,
      checkInCount,
      tierStats,
    };
  },

  async getTickets(eventId: string, filters: {
    page: number;
    limit: number;
    search?: string;
    status?: string;
  }) {
    const where: Prisma.TicketWhereInput = { eventId };
    if (filters.status) where.status = filters.status as never;
    if (filters.search) {
      where.OR = [
        { holderEmail: { contains: filters.search, mode: 'insensitive' } },
        { holderName: { contains: filters.search, mode: 'insensitive' } },
        { ticketCode: { contains: filters.search, mode: 'insensitive' } },
      ];
    }

    const [tickets, total] = await Promise.all([
      prisma.ticket.findMany({
        where,
        skip: (filters.page - 1) * filters.limit,
        take: filters.limit,
        orderBy: { issuedAt: 'desc' },
        include: {
          tier: { select: { name: true, tierType: true } },
          order: { select: { id: true, amountCAD: true, status: true } },
        },
      }),
      prisma.ticket.count({ where }),
    ]);

    return {
      tickets,
      pagination: {
        page: filters.page,
        limit: filters.limit,
        total,
        totalPages: Math.ceil(total / filters.limit),
      },
    };
  },

  async getCheckIns(eventId: string, filters: { page: number; limit: number }) {
    const [checkIns, total] = await Promise.all([
      prisma.checkIn.findMany({
        where: { eventId },
        skip: (filters.page - 1) * filters.limit,
        take: filters.limit,
        orderBy: { checkedInAt: 'desc' },
        include: {
          ticket: {
            select: { ticketCode: true, holderName: true, holderEmail: true },
          },
          checkedInBy: { select: { id: true, name: true } },
        },
      }),
      prisma.checkIn.count({ where: { eventId } }),
    ]);

    return {
      checkIns,
      pagination: {
        page: filters.page,
        limit: filters.limit,
        total,
        totalPages: Math.ceil(total / filters.limit),
      },
    };
  },

  // --- Availability ---

  async getAvailability(eventId: string) {
    const event = await prisma.ticketedEvent.findUnique({
      where: { id: eventId },
      include: {
        ticketTiers: {
          where: { isActive: true },
          orderBy: { sortOrder: 'asc' },
        },
      },
    });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');

    const now = new Date();
    const tiers = event.ticketTiers.map(t => {
      const available = t.maxQuantity ? t.maxQuantity - t.soldCount - (t.reservedCount || 0) : null;
      const onSale = (!t.salesStartAt || t.salesStartAt <= now) &&
                     (!t.salesEndAt || t.salesEndAt >= now);
      return {
        id: t.id,
        name: t.name,
        tierType: t.tierType,
        priceCAD: t.priceCAD,
        minDonationCAD: t.minDonationCAD,
        maxPerOrder: t.maxPerOrder,
        available,
        soldOut: available !== null && available <= 0,
        onSale,
      };
    });

    const eventSoldOut = event.maxAttendees
      ? event.currentAttendees >= event.maxAttendees
      : false;

    return { eventId, eventSoldOut, tiers };
  },

  // --- Meeting Access ---

  async getMeetingAccess(slug: string, ticketCode: string) {
    const event = await prisma.ticketedEvent.findUnique({
      where: { slug },
      include: {
        meeting: { select: { jitsiRoom: true, isActive: true } },
      },
    });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');
    if (event.status !== 'PUBLISHED') throw new AppError(404, 'Event not found', 'NOT_FOUND');
    if (event.eventFormat === 'IN_PERSON') throw new AppError(400, 'This event does not have online access', 'NO_ONLINE_ACCESS');
    if (!event.meeting || !event.meeting.isActive) throw new AppError(400, 'Meeting room is not active', 'MEETING_INACTIVE');

    // Validate ticket code
    const ticket = await prisma.ticket.findUnique({ where: { ticketCode } });
    if (!ticket || ticket.eventId !== event.id) {
      throw new AppError(403, 'Invalid ticket code', 'INVALID_TICKET');
    }
    if (ticket.status !== 'VALID' && ticket.status !== 'CHECKED_IN') {
      throw new AppError(403, 'Ticket is not valid', 'TICKET_INVALID_STATUS');
    }

    const domain = env.DOMAIN || 'cmlite.org';
    return {
      jitsiRoom: event.meeting.jitsiRoom,
      domain: `meet.${domain}`,
      eventTitle: event.title,
    };
  },

  async getModeratorToken(eventId: string, user: { id: string; email: string; name: string | null }) {
    const event = await prisma.ticketedEvent.findUnique({
      where: { id: eventId },
      include: { meeting: true },
    });
    if (!event) throw new AppError(404, 'Event not found', 'NOT_FOUND');
    if (!event.meeting) throw new AppError(400, 'No meeting room associated with this event', 'NO_MEETING');

    const token = generateModeratorToken(user, event.meeting.jitsiRoom);
    const domain = env.DOMAIN || 'cmlite.org';

    return {
      token,
      jitsiUrl: `https://meet.${domain}/${event.meeting.jitsiRoom}?jwt=${token}`,
    };
  },

};
