import { COOKIE_NAME } from "@shared/const";
import { getSessionCookieOptions } from "./_core/cookies";
import { systemRouter } from "./_core/systemRouter";
import { protectedProcedure, publicProcedure, router } from "./_core/trpc";
import { approveMtnPayment, attachPaystackReference, checkInTicket, createComplimentaryInvite, createEventWithTicket, createOrder, fulfillPaystackOrder, getEventBySlug, updateEvent, updateEventStatus, getOrganizerSummary, getOrder, getTicketByToken, listAttendeeTickets, listEventTicketTypes, listPendingPayments, listPublishedEvents, resendTicketEmail, resendTicketSms, submitMtnPayment } from "./db";
import { z } from "zod";
import { storagePut } from "./storage";
import { isValidGhanaPhone, normalizeGhanaPhone } from "./mnotify";
import { initializePaystackTransaction, isSuccessfulPaystackPayment, makePaystackReference, verifyPaystackTransaction } from "./paystack";

export const appRouter = router({
  system: systemRouter,
  auth: router({ me: publicProcedure.query(opts => opts.ctx.user), logout: publicProcedure.mutation(({ ctx }) => { const cookieOptions = getSessionCookieOptions(ctx.req); ctx.res.clearCookie(COOKIE_NAME, { ...cookieOptions, maxAge: -1 }); return { success: true } as const; }) }),
  events: router({
    uploadBanner: protectedProcedure.input(z.object({
      fileName: z.string().min(1).max(180),
      contentType: z.enum(["image/jpeg", "image/png", "image/webp", "image/gif"]),
      data: z.string().min(1),
    })).mutation(async ({ ctx, input }) => {
      const rawBase64 = input.data.replace(/^data:[^;]+;base64,/, "");
      const data = Buffer.from(rawBase64, "base64");
      if (data.length === 0 || data.length > 8 * 1024 * 1024) {
        throw new Error("Banner image must be between 1 byte and 8 MB.");
      }
      const extension = input.contentType.split("/")[1] === "jpeg" ? "jpg" : input.contentType.split("/")[1];
      const safeName = input.fileName.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/-+/g, "-").slice(0, 120) || `banner.${extension}`;
      const stored = await storagePut(`event-banners/${ctx.user.id}/${Date.now()}-${safeName}`, data, input.contentType);
      return { url: stored.url, key: stored.key };
    }),
    list: publicProcedure.query(() => listPublishedEvents()),
    bySlug: publicProcedure.input(z.object({ slug: z.string().min(1) })).query(({ input }) => getEventBySlug(input.slug)),
    ticketTypes: publicProcedure.input(z.object({ eventId: z.number().int().positive() })).query(({ input }) => listEventTicketTypes(input.eventId)),
    create: protectedProcedure.input(z.object({ title: z.string().min(3), slug: z.string().min(3), description: z.string().optional(), category: z.string().min(2), location: z.string().min(2), startsAt: z.date(), endsAt: z.date().optional(), bannerUrl: z.string().refine((value) => /^https?:\/\//.test(value) || value.startsWith("/manus-storage/"), "Banner must be a public URL or uploaded image.").optional(), status: z.enum(["draft", "published"]).default("draft"), ticketName: z.string().min(2), ticketDescription: z.string().optional(), priceMinor: z.number().int().nonnegative(), quantityLimit: z.number().int().positive().max(100000), additionalTicketName: z.string().optional(), additionalTicketDescription: z.string().optional(), additionalPriceMinor: z.number().int().nonnegative().optional(), additionalQuantityLimit: z.number().int().positive().max(100000).optional() })).mutation(({ ctx, input }) => { const { ticketName, ticketDescription, priceMinor, quantityLimit, additionalTicketName, additionalTicketDescription, additionalPriceMinor, additionalQuantityLimit, ...event } = input; return createEventWithTicket({ ...event, organizerId: ctx.user.id }, { name: ticketName, description: ticketDescription, priceMinor, quantityLimit }, additionalTicketName ? [{ name: additionalTicketName, description: additionalTicketDescription, priceMinor: additionalPriceMinor ?? 0, quantityLimit: additionalQuantityLimit ?? quantityLimit }] : []); }),
    organizerSummary: protectedProcedure.query(({ ctx }) => getOrganizerSummary(ctx.user.id)),
    sendComplimentary: protectedProcedure.input(z.object({ eventId: z.number().int().positive(), ticketTypeId: z.number().int().positive(), recipientEmail: z.string().email(), recipientPhone: z.string().refine(isValidGhanaPhone, "Enter a valid Ghanaian mobile number."), recipientName: z.string().optional() })).mutation(({ ctx, input }) => createComplimentaryInvite({ ...input, recipientPhone: normalizeGhanaPhone(input.recipientPhone), organizerId: ctx.user.id, status: "pending" })),
    pendingPayments: protectedProcedure.query(({ ctx }) => listPendingPayments(ctx.user.id)),
    reviewPayment: protectedProcedure.input(z.object({ paymentId: z.number().int().positive(), decision: z.enum(["approved", "rejected"]), reviewNote: z.string().max(500).optional() })).mutation(({ ctx, input }) => approveMtnPayment(input.paymentId, ctx.user.id, ctx.user.role, input.decision, input.reviewNote)),
    updateStatus: protectedProcedure.input(z.object({ eventId: z.number().int().positive(), status: z.enum(["draft", "published", "cancelled"]) })).mutation(({ ctx, input }) => updateEventStatus(input.eventId, ctx.user.id, ctx.user.role, input.status)),
    checkIn: protectedProcedure.input(z.object({ qrToken: z.string().min(8).max(140) })).mutation(({ ctx, input }) => checkInTicket(input.qrToken, ctx.user.id, ctx.user.role)),
    update: protectedProcedure.input(z.object({ eventId: z.number().int().positive(), title: z.string().min(3), description: z.string().optional(), category: z.string().min(2), location: z.string().min(2), startsAt: z.date(), endsAt: z.date().optional(), bannerUrl: z.string().refine((value) => /^https?:\/\//.test(value) || value.startsWith("/manus-storage/"), "Banner must be a public URL or uploaded image.").optional() })).mutation(({ ctx, input }) => { const { eventId, ...event } = input; return updateEvent(eventId, ctx.user.id, ctx.user.role, event); }),
  }),
  tickets: router({ mine: protectedProcedure.query(({ ctx }) => ctx.user.email ? listAttendeeTickets(ctx.user.email, ctx.user.id) : listAttendeeTickets("", ctx.user.id)), byToken: publicProcedure.input(z.object({ token: z.string().min(12).max(140) })).query(({ input }) => getTicketByToken(input.token)) }),
  checkout: router({
    createOrder: publicProcedure.input(z.object({ eventId: z.number().int().positive(), ticketTypeId: z.number().int().positive(), quantity: z.number().int().min(1).max(10), buyerName: z.string().min(2), buyerEmail: z.string().email(), buyerPhone: z.string().refine(isValidGhanaPhone, "Enter a valid Ghanaian mobile number."), totalMinor: z.number().int().nonnegative() })).mutation(({ input, ctx }) => createOrder({ ...input, buyerPhone: normalizeGhanaPhone(input.buyerPhone), buyerId: ctx.user?.id ?? null, status: input.totalMinor === 0 ? "paid" : "pending" })),
    /** @deprecated Retained only to validate and read historical MTN records; new checkouts use Paystack. */
    submitMtnPayment: publicProcedure.input(z.object({ orderId: z.number().int().positive(), payerName: z.string().min(2), payerPhone: z.string().refine(isValidGhanaPhone, "Enter a valid Ghanaian mobile number."), transactionId: z.string().min(4).max(140), amountMinor: z.number().int().positive() })).mutation(({ input }) => submitMtnPayment({ ...input, payerPhone: normalizeGhanaPhone(input.payerPhone), recipientPhone: "0552250838", status: "pending" })),
    initializePaystack: publicProcedure.input(z.object({ orderId: z.number().int().positive() })).mutation(async ({ input, ctx }) => {
      const order = await getOrder(input.orderId);
      if (!order || order.status !== "pending" || order.totalMinor <= 0) throw new Error("This order is not eligible for Paystack checkout");
      const reference = order.paystackReference ?? makePaystackReference(order.id);
      const attached = await attachPaystackReference(order.id, reference);
      const origin = `${ctx.req.protocol}://${ctx.req.get("host")}`;
      const transaction = await initializePaystackTransaction({ email: attached.buyerEmail, amountMinor: attached.totalMinor, currency: attached.currency, reference, callbackUrl: `${origin}/order/${attached.id}?reference=${encodeURIComponent(reference)}`, metadata: { orderId: attached.id, eventId: attached.eventId, ticketTypeId: attached.ticketTypeId } });
      return { orderId: attached.id, reference, authorizationUrl: transaction.authorization_url };
    }),
    verifyPaystack: publicProcedure.input(z.object({ orderId: z.number().int().positive(), reference: z.string().min(8).max(120) })).mutation(async ({ input }) => {
      const order = await getOrder(input.orderId);
      if (!order || order.paystackReference !== input.reference) throw new Error("Paystack reference does not match this order");
      const transaction = await verifyPaystackTransaction(input.reference);
      if (!isSuccessfulPaystackPayment(transaction, { amountMinor: order.totalMinor, currency: order.currency })) throw new Error("Paystack payment is not complete or does not match this order");
      return fulfillPaystackOrder(order.id, input.reference);
    }),
    orderStatus: publicProcedure.input(z.object({ orderId: z.number().int().positive() })).query(({ input }) => getOrder(input.orderId)),
    resendTicketEmail: protectedProcedure.input(z.object({ orderId: z.number().int().positive() })).mutation(({ ctx, input }) => resendTicketEmail(input.orderId, ctx.user.id, ctx.user.email ?? undefined)),
    resendTicketSms: protectedProcedure.input(z.object({ orderId: z.number().int().positive() })).mutation(({ ctx, input }) => resendTicketSms(input.orderId, ctx.user.id, ctx.user.email ?? undefined)),
  }),
});
export type AppRouter = typeof appRouter;
