import crypto from "crypto";
import { Router } from "express";
import { z } from "zod";
import { logActivity } from "../lib/activity";
import { env } from "../lib/env";
import { emailTemplate, sendEmail } from "../lib/mailer";
import { notifyUser } from "../lib/notifications";
import { positionBetween } from "../lib/position";
import { prisma } from "../lib/prisma";
import { emitToBoard } from "../lib/socketBus";
import { uploadAttachment } from "../lib/upload";
import { HttpError } from "../middleware/errorHandler";
import { optionalAuth } from "../middleware/requireAuth";

export const portalRouter = Router();
portalRouter.use(optionalAuth);

portalRouter.get("/:slug", async (req, res, next) => {
  try {
    const board = await prisma.board.findUnique({
      where: { portalSlug: req.params.slug },
      select: {
        id: true,
        name: true,
        portalEnabled: true,
        portalTitle: true,
        portalWelcomeMessage: true,
        workspace: { select: { name: true, logoUrl: true } },
        portalCategories: {
          orderBy: { position: "asc" },
          include: {
            requestTypes: {
              orderBy: { position: "asc" },
              include: { fields: { orderBy: { position: "asc" } } },
            },
          },
        },
      },
    });
    if (!board || !board.portalEnabled) {
      throw new HttpError(404, "This portal isn't available");
    }

    res.json({
      boardName: board.name,
      title: board.portalTitle || board.name,
      welcomeMessage: board.portalWelcomeMessage,
      workspaceName: board.workspace.name,
      workspaceLogoUrl: board.workspace.logoUrl,
      categories: board.portalCategories,
    });
  } catch (err) {
    next(err);
  }
});

const answerSchema = z.object({ fieldId: z.string(), value: z.string() });

const submitSchema = z.object({
  requestTypeId: z.string().min(1),
  email: z.string().email().optional(),
  name: z.string().min(1).max(100).optional(),
  summary: z.string().min(1).max(200),
  description: z.string().max(10000).optional(),
  answers: z.string().optional(), // JSON-encoded PortalAnswerInput[]
});

portalRouter.post(
  "/:slug/submit",
  uploadAttachment.array("attachments", 5),
  async (req, res, next) => {
    try {
      const board = await prisma.board.findUnique({
        where: { portalSlug: req.params.slug },
        include: {
          lists: { where: { isArchived: false }, orderBy: { position: "asc" }, take: 1 },
        },
      });
      if (!board || !board.portalEnabled) {
        throw new HttpError(404, "This portal isn't available");
      }

      const data = submitSchema.parse(req.body);

      const requestType = await prisma.portalRequestType.findFirst({
        where: { id: data.requestTypeId, category: { boardId: board.id } },
        include: { fields: true },
      });
      if (!requestType) throw new HttpError(404, "Unknown request type");

      let submitterId: string | null = null;
      let submitterEmail = data.email ?? null;
      let submitterName = data.name ?? null;

      if (req.userId) {
        const user = await prisma.user.findUnique({ where: { id: req.userId } });
        if (user) {
          submitterId = user.id;
          submitterEmail = user.email;
          submitterName = user.name;
        }
      }

      if (!submitterEmail) {
        throw new HttpError(400, "Email is required");
      }
      if (!submitterName) {
        submitterName = submitterEmail.split("@")[0];
      }

      const intakeListId = board.portalIntakeListId ?? board.lists[0]?.id;
      if (!intakeListId) throw new HttpError(400, "This board isn't ready to receive requests yet");

      const last = await prisma.card.findFirst({
        where: { listId: intakeListId, isArchived: false },
        orderBy: { position: "desc" },
      });

      let rawAnswers: { fieldId: string; value: string }[] = [];
      if (data.answers) {
        try {
          rawAnswers = z.array(answerSchema).parse(JSON.parse(data.answers));
        } catch {
          rawAnswers = [];
        }
      }

      const portalAnswers = rawAnswers
        .map((a) => {
          const field = requestType.fields.find((f) => f.id === a.fieldId);
          if (!field || !a.value.trim()) return null;
          return { fieldId: field.id, label: field.label, value: a.value.trim() };
        })
        .filter((a): a is { fieldId: string; label: string; value: string } => !!a);

      // Guests (no account) get a random token baked into the card so they can
      // check status later via an emailed link, without ever getting board access.
      const guestTrackingToken = submitterId ? null : crypto.randomBytes(24).toString("hex");

      const card = await prisma.card.create({
        data: {
          listId: intakeListId,
          boardId: board.id,
          title: data.summary,
          description: data.description?.trim() || null,
          position: positionBetween(last?.position, undefined),
          submitterId,
          submitterEmail,
          submitterName,
          portalRequestTypeId: requestType.id,
          guestTrackingToken,
          portalAnswers: { create: portalAnswers },
        },
      });

      const files = (req.files as Express.Multer.File[] | undefined) ?? [];
      if (files.length > 0) {
        await prisma.attachment.createMany({
          data: files.map((f) => ({
            cardId: card.id,
            uploaderId: submitterId,
            type: "file",
            url: `/uploads/attachments/${f.filename}`,
            filename: f.originalname,
            mimeType: f.mimetype,
            sizeBytes: f.size,
          })),
        });
      }

      // Logged-in submitters get a restricted membership so they can find their own
      // ticket on the board later (see the "isPortalRequester" filtering in
      // GET /boards/:id) — never downgrades someone who already has real access.
      if (submitterId) {
        const existingMembership = await prisma.boardMember.findUnique({
          where: { boardId_userId: { boardId: board.id, userId: submitterId } },
        });
        if (!existingMembership) {
          await prisma.boardMember.create({
            data: {
              boardId: board.id,
              userId: submitterId,
              role: "VIEWER",
              isPortalRequester: true,
            },
          });
        }
      }

      if (submitterId) {
        await logActivity({
          boardId: board.id,
          cardId: card.id,
          actorId: submitterId,
          action: "card.created",
        });
      }

      const admins = await prisma.boardMember.findMany({
        where: { boardId: board.id, role: "ADMIN" },
        select: { userId: true },
      });
      for (const admin of admins) {
        await notifyUser({
          recipientId: admin.userId,
          type: "PORTAL_REQUEST",
          message: `New request from ${submitterName}: "${data.summary}"`,
          cardId: card.id,
          boardId: board.id,
        });
      }

      if (guestTrackingToken && submitterEmail) {
        const trackingUrl = `${env.CLIENT_URL}/track/${guestTrackingToken}`;
        sendEmail({
          to: submitterEmail,
          subject: `We've received your request: "${data.summary}"`,
          html: emailTemplate({
            title: "We've received your request",
            bodyHtml: `Hi ${submitterName},<br/><br/>Thanks for reaching out to ${board.portalTitle || board.name} — we've logged your request "<strong>${data.summary}</strong>" and someone will follow up soon.<br/><br/>You can check its status any time using the link below — no account needed.`,
            ctaLabel: "Track my request",
            ctaUrl: trackingUrl,
          }),
        }).catch((err) => console.error("[portal] Failed to send tracking email:", err));
      }

      emitToBoard(board.id, "board:changed", {});
      res.status(201).json({ cardId: card.id, trackingToken: guestTrackingToken });
    } catch (err) {
      next(err);
    }
  }
);

// ---------- Guest ticket tracking (public, token-based, no account/login) ----------

portalRouter.get("/ticket/:token", async (req, res, next) => {
  try {
    const card = await prisma.card.findUnique({
      where: { guestTrackingToken: req.params.token },
      include: {
        list: {
          select: {
            name: true,
            board: { select: { name: true, portalTitle: true, workspace: { select: { name: true, logoUrl: true } } } },
          },
        },
        status: { select: { label: true, color: true } },
        portalRequestType: { select: { name: true } },
        attachments: { select: { id: true, filename: true, url: true, mimeType: true, sizeBytes: true, createdAt: true } },
      },
    });
    if (!card) throw new HttpError(404, "We couldn't find a request for this link");

    res.json({
      ticket: {
        title: card.title,
        description: card.description,
        createdAt: card.createdAt,
        updatedAt: card.updatedAt,
        isCompleted: card.isCompleted,
        stageName: card.list.name,
        statusLabel: card.status?.label ?? null,
        statusColor: card.status?.color ?? null,
        requestTypeName: card.portalRequestType?.name ?? null,
        submitterName: card.submitterName,
        boardName: card.list.board.portalTitle || card.list.board.name,
        workspaceName: card.list.board.workspace.name,
        workspaceLogoUrl: card.list.board.workspace.logoUrl,
        attachments: card.attachments,
      },
    });
  } catch (err) {
    next(err);
  }
});
