/**
 * Magic link routes — mounted at top level to bypass space auth middleware.
 * GET  /respond/:token  — verify HMAC, render poll or RSVP page
 * POST /respond/:token  — submit response, re-render with confirmation
 */

import { Hono } from "hono";
import { verifyMagicToken } from "./token";
import { renderPollPage, renderRsvpPage, renderError } from "./render";
import { syncServer } from "../sync-instance";
import { choicesDocId } from "../../modules/rchoices/schemas";
import { calendarDocId } from "../../modules/rcal/schemas";
import type { ChoicesDoc } from "../../modules/rchoices/schemas";
import type { CalendarDoc } from "../../modules/rcal/schemas";
import type { EventAttendee } from "../../modules/rcal/schemas";

export const magicLinkRoutes = new Hono();

// ── GET /respond/:token ──
magicLinkRoutes.get("/:token", async (c) => {
  const token = c.req.param("token");

  let verified;
  try {
    verified = await verifyMagicToken(token);
  } catch (err) {
    const message = err instanceof Error ? err.message : "Unknown error";
    const title = message === "Token expired" ? "Link Expired" : "Invalid Link";
    const body = message === "Token expired"
      ? "This response link has expired. Request a new invitation."
      : "This link is not valid. It may have been corrupted.";
    return c.html(renderError(title, body), title === "Link Expired" ? 410 : 400);
  }

  try {
    if (verified.type === "poll") {
      return c.html(renderPollForToken(verified, token));
    } else {
      return c.html(renderRsvpForToken(verified, token));
    }
  } catch (err) {
    console.error("[MagicLink] Render error:", err);
    return c.html(renderError("Error", "Could not load the response page."), 500);
  }
});

// ── POST /respond/:token ──
magicLinkRoutes.post("/:token", async (c) => {
  const token = c.req.param("token");

  let verified;
  try {
    verified = await verifyMagicToken(token);
  } catch (err) {
    const message = err instanceof Error ? err.message : "Unknown error";
    const title = message === "Token expired" ? "Link Expired" : "Invalid Link";
    const body = message === "Token expired"
      ? "This response link has expired. Request a new invitation."
      : "This link is not valid. It may have been corrupted.";
    return c.html(renderError(title, body), title === "Link Expired" ? 410 : 400);
  }

  try {
    const formData = await c.req.parseBody();

    if (verified.type === "poll") {
      const choice = formData.choice as string;
      if (!choice) return c.html(renderError("Error", "No choice submitted."), 400);

      const docId = choicesDocId(verified.space);
      const doc = syncServer.getDoc<ChoicesDoc>(docId);
      if (!doc) return c.html(renderError("Not Found", "Poll data not found."), 404);

      const session = doc.sessions[verified.targetId];
      if (!session) return c.html(renderError("Not Found", "Poll session not found."), 404);

      // Validate choice is a valid option
      if (!session.options.some((o) => o.id === choice)) {
        return c.html(renderError("Error", "Invalid option selected."), 400);
      }

      // Write vote keyed by sessionId:magic:tokenHash (dedup by token)
      const voteKey = `${verified.targetId}:magic:${verified.tokenHash}`;
      syncServer.changeDoc<ChoicesDoc>(docId, `magic link vote by ${verified.respondentName}`, (d) => {
        d.votes[voteKey] = {
          participantDid: `magic:${verified.tokenHash}`,
          choices: { [choice]: 1 },
          updatedAt: Date.now(),
        };
      });

      return c.html(renderPollForToken(verified, token, choice));

    } else {
      // RSVP
      const status = formData.status as string;
      if (!status || !["yes", "no", "maybe"].includes(status)) {
        return c.html(renderError("Error", "Invalid RSVP status."), 400);
      }

      const docId = calendarDocId(verified.space);
      const doc = syncServer.getDoc<CalendarDoc>(docId);
      if (!doc) return c.html(renderError("Not Found", "Calendar data not found."), 404);

      const event = doc.events[verified.targetId];
      if (!event) return c.html(renderError("Not Found", "Event not found."), 404);

      syncServer.changeDoc<CalendarDoc>(docId, `magic link RSVP by ${verified.respondentName}`, (d) => {
        const ev = d.events[verified.targetId];
        const attendees = (ev.attendees || []) as EventAttendee[];

        // Find existing response by tokenHash (update, not duplicate)
        const existingIdx = attendees.findIndex((a) => a.tokenHash === verified.tokenHash);
        const attendee: EventAttendee = {
          name: verified.respondentName,
          status: status as "yes" | "no" | "maybe",
          respondedAt: Date.now(),
          source: "magic-link",
          tokenHash: verified.tokenHash,
        };

        if (existingIdx >= 0) {
          attendees[existingIdx] = attendee;
        } else {
          attendees.push(attendee);
        }

        ev.attendees = attendees;
        ev.attendeeCount = attendees.filter((a) => a.status === "yes").length;
      });

      return c.html(renderRsvpForToken(verified, token, status));
    }
  } catch (err) {
    console.error("[MagicLink] Submit error:", err);
    return c.html(renderError("Error", "Could not submit your response. Please try again."), 500);
  }
});

// ── Helpers ──

function renderPollForToken(
  verified: { space: string; targetId: string; respondentName: string; tokenHash: string },
  token: string,
  justVoted?: string,
): string {
  const docId = choicesDocId(verified.space);
  const doc = syncServer.getDoc<ChoicesDoc>(docId);
  if (!doc) return renderError("Not Found", "Poll data not found.");

  const session = doc.sessions[verified.targetId];
  if (!session) return renderError("Not Found", "Poll session not found.");

  // Check for existing vote by this token
  const voteKey = `${verified.targetId}:magic:${verified.tokenHash}`;
  const existingVote = doc.votes[voteKey];
  const existingChoice = existingVote
    ? Object.entries(existingVote.choices).find(([, v]) => v === 1)?.[0]
    : undefined;

  return renderPollPage(session, token, verified.respondentName, existingChoice, justVoted);
}

function renderRsvpForToken(
  verified: { space: string; targetId: string; respondentName: string; tokenHash: string },
  token: string,
  justResponded?: string,
): string {
  const docId = calendarDocId(verified.space);
  const doc = syncServer.getDoc<CalendarDoc>(docId);
  if (!doc) return renderError("Not Found", "Calendar data not found.");

  const event = doc.events[verified.targetId];
  if (!event) return renderError("Not Found", "Event not found.");

  // Check for existing RSVP by this token
  const attendees = (event.attendees || []) as EventAttendee[];
  const existing = attendees.find((a) => a.tokenHash === verified.tokenHash);
  const currentStatus = existing?.status;

  return renderRsvpPage(event, token, verified.respondentName, currentStatus, justResponded);
}
