import { Router, Request, Response, NextFunction } from 'express';
import { validate } from '../../middleware/validate';
import { optionalAuth } from '../../middleware/auth.middleware';
import { ticketedEventsService } from './ticketed-events.service';
import { ticketsService } from './tickets.service';
import { ticketEmailService } from './ticket-email.service';
import { checkoutSchema, registerFreeSchema } from './ticketed-events.schemas';
import { getStripe } from '../../services/stripe.client';
import { prisma } from '../../config/database';
import { env } from '../../config/env';
import { AppError } from '../../middleware/error-handler';
import { paymentCheckoutRateLimit } from '../../middleware/rate-limit';
import { requirePaymentsEnabled } from '../payments/payment-settings.service';

const router = Router();

// GET / — list published events
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const page = parseInt(req.query.page as string) || 1;
    const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
    const upcoming = req.query.upcoming === 'true';
    const result = await ticketedEventsService.listPublished({ page, limit, upcoming });
    res.json(result);
  } catch (err) { next(err); }
});

// GET /my-tickets — authenticated user's tickets (must be before /:slug)
router.get('/my-tickets', optionalAuth, async (req: Request, res: Response, next: NextFunction) => {
  try {
    if (!req.user) {
      throw new AppError(401, 'Authentication required', 'UNAUTHORIZED');
    }
    const tickets = await ticketsService.getUserTickets(req.user.id);
    const ticketsWithQr = tickets.map(t => ({
      ...t,
      qrUrl: `${env.API_URL}/api/qr?text=${encodeURIComponent(t.ticketCode)}&size=300`,
    }));
    res.json({ tickets: ticketsWithQr });
  } catch (err) { next(err); }
});

// GET /:slug — event detail by slug
router.get('/:slug', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const slug = req.params.slug as string;
    const event = await ticketedEventsService.findBySlug(slug);

    // Check private event access
    if (event.visibility === 'PRIVATE') {
      const inviteCode = req.query.inviteCode as string | undefined;
      if (!inviteCode || inviteCode !== event.inviteCode) {
        // Return limited info without invite code
        res.json({
          id: event.id,
          slug: event.slug,
          title: event.title,
          visibility: 'PRIVATE',
          requiresInviteCode: true,
        });
        return;
      }
    }

    // Only show published or completed events publicly
    if (event.status !== 'PUBLISHED' && event.status !== 'COMPLETED') {
      throw new AppError(404, 'Event not found', 'NOT_FOUND');
    }

    // Include format + hasMeeting (but NOT the room name — that's gated)
    const { meeting, ...rest } = event;
    res.json({
      ...rest,
      hasMeeting: !!(meeting?.isActive),
    });
  } catch (err) { next(err); }
});

// GET /:slug/meeting-access — ticket-gated Jitsi room access
router.get('/:slug/meeting-access', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const slug = req.params.slug as string;
    const ticketCode = req.query.ticketCode as string;
    if (!ticketCode) {
      throw new AppError(400, 'ticketCode query parameter is required', 'MISSING_TICKET_CODE');
    }
    const access = await ticketedEventsService.getMeetingAccess(slug, ticketCode);
    res.json(access);
  } catch (err) { next(err); }
});

// GET /:slug/availability — ticket availability
router.get('/:slug/availability', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const slug = req.params.slug as string;
    const event = await prisma.ticketedEvent.findUnique({ where: { slug } });
    if (!event || event.status !== 'PUBLISHED') {
      throw new AppError(404, 'Event not found', 'NOT_FOUND');
    }
    const availability = await ticketedEventsService.getAvailability(event.id);
    res.json(availability);
  } catch (err) { next(err); }
});

// POST /:slug/checkout — create Stripe checkout for paid ticket
router.post('/:slug/checkout', requirePaymentsEnabled, paymentCheckoutRateLimit, optionalAuth, validate(checkoutSchema), async (req: Request, res: Response, next: NextFunction) => {
  try {
    const slug = req.params.slug as string;
    const { tierId, quantity, buyerEmail, buyerName } = req.body;

    const event = await prisma.ticketedEvent.findUnique({ where: { slug } });
    if (!event || event.status !== 'PUBLISHED') {
      throw new AppError(404, 'Event not found', 'NOT_FOUND');
    }

    const tier = await prisma.ticketTier.findUnique({ where: { id: tierId } });
    if (!tier || tier.eventId !== event.id) {
      throw new AppError(400, 'Invalid tier', 'INVALID_TIER');
    }
    if (tier.tierType === 'FREE') {
      throw new AppError(400, 'Use /register for free tickets', 'USE_REGISTER');
    }

    // Check sales window
    const now = new Date();
    if (tier.salesStartAt && tier.salesStartAt > now) {
      throw new AppError(400, 'Ticket sales have not started yet', 'NOT_ON_SALE');
    }
    if (tier.salesEndAt && tier.salesEndAt < now) {
      throw new AppError(400, 'Ticket sales have ended', 'SALES_ENDED');
    }

    // Atomically reserve capacity (soldCount + reservedCount checked together)
    // This prevents overselling from concurrent Stripe checkout sessions
    const reserved = await prisma.$transaction(async (tx) => {
      const currentTier = await tx.ticketTier.findUnique({ where: { id: tierId } });
      if (!currentTier) throw new AppError(400, 'Tier not found', 'NOT_FOUND');

      const effectiveSold = currentTier.soldCount + currentTier.reservedCount;
      if (currentTier.maxQuantity && effectiveSold + quantity > currentTier.maxQuantity) {
        throw new AppError(400, 'Not enough tickets available', 'SOLD_OUT');
      }

      const currentEvent = await tx.ticketedEvent.findUnique({ where: { id: event.id } });
      if (currentEvent?.maxAttendees && currentEvent.currentAttendees + currentTier.reservedCount + quantity > currentEvent.maxAttendees) {
        throw new AppError(400, 'Event is at full capacity', 'SOLD_OUT');
      }

      await tx.ticketTier.update({
        where: { id: tierId },
        data: { reservedCount: { increment: quantity } },
      });

      return currentTier;
    });

    const unitAmount = reserved.tierType === 'DONATION'
      ? Math.max(reserved.priceCAD, reserved.minDonationCAD || 0)
      : reserved.priceCAD;

    const stripe = await getStripe();
    const session = await stripe.checkout.sessions.create({
      mode: 'payment',
      line_items: [{
        price_data: {
          currency: 'cad',
          product_data: {
            name: `${event.title} — ${tier.name}`,
            description: tier.description || `Ticket for ${event.title}`,
          },
          unit_amount: unitAmount,
        },
        quantity,
      }],
      customer_email: buyerEmail,
      success_url: `${env.ADMIN_URL}/event/${slug}/ticket/{CHECKOUT_SESSION_ID}?success=true`,
      cancel_url: `${env.ADMIN_URL}/event/${slug}`,
      metadata: {
        type: 'event_ticket',
        eventId: event.id,
        tierId: tier.id,
        quantity: String(quantity),
        buyerEmail,
        buyerName: buyerName || '',
        userId: req.user?.id || '',
      },
      expires_after: 1800, // 30 minutes
    } as never);

    // Create pending order
    await prisma.order.create({
      data: {
        userId: req.user?.id || null,
        amountCAD: unitAmount * quantity,
        status: 'PENDING',
        stripeCheckoutSessionId: session.id,
        type: 'event_ticket',
        buyerEmail,
        buyerName: buyerName || null,
      },
    });

    res.json({ sessionId: session.id, url: session.url });
  } catch (err) { next(err); }
});

// POST /:slug/register — register for free ticket (no Stripe)
router.post('/:slug/register', optionalAuth, validate(registerFreeSchema), async (req: Request, res: Response, next: NextFunction) => {
  try {
    const slug = req.params.slug as string;
    const { tierId, quantity, holderEmail, holderName } = req.body;

    const event = await prisma.ticketedEvent.findUnique({ where: { slug } });
    if (!event || event.status !== 'PUBLISHED') {
      throw new AppError(404, 'Event not found', 'NOT_FOUND');
    }

    const tier = await prisma.ticketTier.findUnique({ where: { id: tierId } });
    if (!tier || tier.eventId !== event.id) {
      throw new AppError(400, 'Invalid tier', 'INVALID_TIER');
    }
    if (tier.tierType !== 'FREE') {
      throw new AppError(400, 'This tier requires payment', 'REQUIRES_PAYMENT');
    }

    const tickets = await ticketsService.createTickets({
      eventId: event.id,
      tierId,
      quantity,
      holderEmail,
      holderName,
      userId: req.user?.id,
    });

    // Send confirmation emails (fire-and-forget)
    for (const ticket of tickets) {
      ticketEmailService.sendTicketConfirmation({
        holderEmail: ticket.holderEmail,
        holderName: ticket.holderName,
        ticketCode: ticket.ticketCode,
        token: (ticket as Record<string, unknown>).token as string,
        eventTitle: event.title,
        eventDate: event.date,
        eventStartTime: event.startTime,
        eventEndTime: event.endTime,
        eventSlug: event.slug,
        venueName: event.venueName,
        venueAddress: event.venueAddress,
        tierName: tier.name,
        eventFormat: event.eventFormat,
      }).catch(() => {});
    }

    res.status(201).json({
      tickets: tickets.map(t => ({
        id: t.id,
        ticketCode: t.ticketCode,
        holderEmail: t.holderEmail,
        holderName: t.holderName,
        status: t.status,
      })),
    });
  } catch (err) { next(err); }
});

// GET /:slug/ticket/:ticketCode — ticket confirmation page data
// Requires authentication or matching holder email to prevent PII enumeration
router.get('/:slug/ticket/:ticketCode', optionalAuth, async (req: Request, res: Response, next: NextFunction) => {
  try {
    const ticket = await ticketsService.findByCode(req.params.ticketCode as string);
    if (ticket.event.slug !== req.params.slug) {
      throw new AppError(404, 'Ticket not found', 'NOT_FOUND');
    }

    // Only return full details if user is authenticated and is the ticket holder or an admin
    const isHolder = req.user && (
      req.user.id === ticket.userId ||
      req.user.email === ticket.holderEmail
    );
    const userRoles = req.user?.roles || (req.user?.role ? [req.user.role] : []);
    const isAdmin = userRoles.some((r: string) => ['SUPER_ADMIN', 'EVENTS_ADMIN'].includes(r));

    if (!isHolder && !isAdmin) {
      // Return minimal non-PII data for unauthenticated/unrelated users
      res.json({
        ticketCode: ticket.ticketCode,
        status: ticket.status,
        event: {
          slug: ticket.event.slug,
          title: ticket.event.title,
          date: ticket.event.date,
          startTime: ticket.event.startTime,
          endTime: ticket.event.endTime,
          venueName: ticket.event.venueName,
        },
        tier: ticket.tier,
      });
      return;
    }

    // Full details for authenticated ticket holder or admin
    const qrUrl = `${env.API_URL}/api/qr?text=${encodeURIComponent(ticket.ticketCode)}&size=300`;
    res.json({ ...ticket, qrUrl });
  } catch (err) { next(err); }
});

export { router as ticketedEventsPublicRouter };
