import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";

/**
 * Phase 1 exit catalog — every HTTP handler must be classified so a new
 * endpoint cannot ship without an isolation story. Tenant-scoped routes
 * delegate to `@slackwsh/messaging` / WorkspacesService, which
 * isolation.integration.test.ts exercises with a foreign workspace_id.
 */
export type IsolationGate =
  | "public"
  | "auth"
  | "user"
  | "tenant"
  | "webhook";

export const HTTP_ROUTE_GATES: Record<string, IsolationGate> = {
  "GET /health": "public",
  "GET /profile-photos/:userId/:photoName": "public",

  "POST /auth/signup": "auth",
  "POST /auth/verify-email": "auth",
  "POST /auth/login": "auth",
  "POST /auth/refresh": "auth",
  "POST /auth/logout": "auth",
  "POST /auth/magic-link/request": "auth",
  "POST /auth/magic-link/consume": "auth",
  "POST /auth/password-reset/request": "auth",
  "POST /auth/password-reset/confirm": "auth",
  "GET /auth/devices": "auth",
  "POST /auth/devices/:sessionId/revoke": "auth",

  "GET /profile": "user",
  "POST /profile/photo": "user",
  "POST /profile/photo/remove": "user",
  "GET /push/vapid-public-key": "user",
  "POST /push/subscribe": "user",
  "DELETE /push/subscribe": "user",
  "GET /integrations": "user",
  "POST /integrations/openai": "user",
  "POST /integrations/disconnect": "user",
  "POST /integrations/rewrite": "user",
  "POST /integrations/oauth/start": "user",
  "GET /integrations/oauth/:provider/callback": "user",
  "GET /integrations/calendar/external": "user",
  "POST /integrations/zoom/meetings": "user",
  "POST /workspaces": "user",
  "GET /workspaces/mine": "user",
  "GET /invites/pending": "user",
  "POST /invites/accept": "user",
  "POST /invites/accept-by-id": "user",
  "POST /invites/decline": "user",
  "GET /notifications": "user",
  "POST /notifications/:notificationId/read": "user",

  "POST /webhooks/livekit": "webhook",
  "POST /webhooks/twilio/voice": "webhook",

  "GET /workspaces/:workspaceId/admin/overview": "tenant",
  "GET /workspaces/:workspaceId/members": "tenant",
  "GET /workspaces/:workspaceId/presence": "tenant",
  "GET /workspaces/:workspaceId/me/status": "tenant",
  "POST /workspaces/:workspaceId/me/status": "tenant",
  "POST /workspaces/:workspaceId/me/availability": "tenant",
  "POST /workspaces/:workspaceId/me/status/context": "tenant",
  "POST /workspaces/:workspaceId/me/status/automatic": "tenant",
  "POST /workspaces/:workspaceId/me/status/scheduled": "tenant",
  "POST /workspaces/:workspaceId/me/status/scheduled/:scheduleId": "tenant",
  "POST /workspaces/:workspaceId/me/status/scheduled/:scheduleId/delete": "tenant",
  "POST /workspaces/:workspaceId/invites": "tenant",
  "POST /workspaces/:workspaceId/members/:targetUserId/role": "tenant",
  "POST /workspaces/:workspaceId/members/:targetUserId/deactivate": "tenant",
  "POST /workspaces/:workspaceId/members/:targetUserId/reactivate": "tenant",
  "POST /workspaces/:workspaceId/transfer-ownership": "tenant",
  "POST /workspaces/:workspaceId/settings": "tenant",

  "POST /workspaces/:workspaceId/channels": "tenant",
  "GET /workspaces/:workspaceId/channels": "tenant",
  "GET /workspaces/:workspaceId/channels/:channelId/members": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/members": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/members/:memberUserId/remove": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/join": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/leave": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/archive": "tenant",
  "GET /workspaces/:workspaceId/channels/:channelId/about": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/prefs": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/integrations": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/integrations/:integrationId/remove": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId": "tenant",
  "DELETE /workspaces/:workspaceId/channels/:channelId/permanent": "tenant",
  "POST /workspaces/:workspaceId/dms": "tenant",
  "POST /workspaces/:workspaceId/dms/group": "tenant",
  "POST /workspaces/:workspaceId/dms/:channelId/closed": "tenant",
  "POST /workspaces/:workspaceId/dms/:channelId/convert": "tenant",

  "POST /workspaces/:workspaceId/channels/:channelId/messages": "tenant",
  "GET /workspaces/:workspaceId/channels/:channelId/messages": "tenant",
  "GET /workspaces/:workspaceId/channels/:channelId/messages/:messageId/thread": "tenant",
  "GET /workspaces/:workspaceId/channels/:channelId/messages/:messageId": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/edit": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/forward": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/delete": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/reactions": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/reactions/remove": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/pin": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/unpin": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/create-task": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/thread-read": "tenant",
  "GET /workspaces/:workspaceId/channels/:channelId/messages/:messageId/thread-status": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/thread-following": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/messages/:messageId/unread": "tenant",
  "GET /workspaces/:workspaceId/threads": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/read": "tenant",

  "POST /workspaces/:workspaceId/channels/:channelId/files/presign": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/files/:fileId/bytes": "tenant",
  "POST /workspaces/:workspaceId/channels/:channelId/files/:fileId/complete": "tenant",
  "GET /workspaces/:workspaceId/channels/:channelId/files/:fileId/access": "tenant",
  "GET /workspaces/:workspaceId/channels/:channelId/files/:fileId/:name": "tenant",
  "DELETE /workspaces/:workspaceId/channels/:channelId/files/:fileId": "tenant",
  "GET /workspaces/:workspaceId/files": "tenant",

  "GET /workspaces/:workspaceId/state/notification-preferences": "tenant",
  "POST /workspaces/:workspaceId/state/notification-preferences": "tenant",
  "GET /workspaces/:workspaceId/state/saved-items": "tenant",
  "POST /workspaces/:workspaceId/state/saved-items/:messageId": "tenant",
  "DELETE /workspaces/:workspaceId/state/saved-items/:messageId": "tenant",
  "GET /workspaces/:workspaceId/state/drafts": "tenant",
  "POST /workspaces/:workspaceId/state/drafts": "tenant",
  "POST /workspaces/:workspaceId/state/drafts/delete": "tenant",

  "GET /workspaces/:workspaceId/search/mentions": "tenant",
  "POST /workspaces/:workspaceId/search/activity/read": "tenant",
  "GET /workspaces/:workspaceId/search/unreads": "tenant",
  "GET /workspaces/:workspaceId/search": "tenant",

  "POST /workspaces/:workspaceId/tasks": "tenant",
  "GET /workspaces/:workspaceId/tasks": "tenant",
  "GET /workspaces/:workspaceId/tasks/unread-assigned": "tenant",
  "POST /workspaces/:workspaceId/tasks/mark-assigned-seen": "tenant",
  "GET /workspaces/:workspaceId/tasks/:taskId": "tenant",
  "POST /workspaces/:workspaceId/tasks/:taskId/update": "tenant",
  "POST /workspaces/:workspaceId/tasks/:taskId/assign": "tenant",
  "POST /workspaces/:workspaceId/tasks/:taskId/status": "tenant",
  "POST /workspaces/:workspaceId/tasks/:taskId/delete": "tenant",
  "POST /workspaces/:workspaceId/tasks/:taskId/restore": "tenant",

  "POST /workspaces/:workspaceId/events": "tenant",
  "GET /workspaces/:workspaceId/events": "tenant",
  "GET /workspaces/:workspaceId/events/:eventId": "tenant",
  "POST /workspaces/:workspaceId/events/:eventId/update": "tenant",
  "POST /workspaces/:workspaceId/events/:eventId/invite": "tenant",
  "POST /workspaces/:workspaceId/events/:eventId/rsvp": "tenant",
  "POST /workspaces/:workspaceId/events/:eventId/delete": "tenant",

  "POST /workspaces/:workspaceId/calls": "tenant",
  "POST /workspaces/:workspaceId/calls/connect": "tenant",
  "GET /workspaces/:workspaceId/calls": "tenant",
  "GET /workspaces/:workspaceId/calls/contacts": "tenant",
  "GET /workspaces/:workspaceId/calls/unseen": "tenant",
  "POST /workspaces/:workspaceId/calls/seen": "tenant",
  "GET /workspaces/:workspaceId/calls/:callId": "tenant",
  "POST /workspaces/:workspaceId/calls/:callId/media-token": "tenant",
  "POST /workspaces/:workspaceId/calls/:callId/recording/start": "tenant",
  "POST /workspaces/:workspaceId/calls/:callId/recording/stop": "tenant",
  "POST /workspaces/:workspaceId/calls/:callId/dial": "tenant",
  "POST /workspaces/:workspaceId/calls/:callId/join": "tenant",
  "POST /workspaces/:workspaceId/calls/:callId/decline": "tenant",
  "POST /workspaces/:workspaceId/calls/:callId/leave": "tenant",
  "POST /workspaces/:workspaceId/calls/:callId/end": "tenant",
  "POST /workspaces/:workspaceId/calls/:callId/invite": "tenant",
};

function walk(dir: string, acc: string[] = []): string[] {
  for (const entry of readdirSync(dir, { withFileTypes: true })) {
    const path = join(dir, entry.name);
    if (entry.isDirectory()) walk(path, acc);
    else if (entry.name.endsWith(".controller.ts")) acc.push(path);
  }
  return acc;
}

function extractRoutes(src: string): string[] {
  const routes: string[] = [];
  const controllerRe = /@Controller(?:\((?:"([^"]*)"|'([^']*)')\))?/g;
  const methodRe = /@(Get|Post|Put|Patch|Delete)\((?:(?:"([^"]*)"|'([^']*)'))?\)/g;
  let controller: RegExpExecArray | null;
  const controllers: Array<{ prefix: string; index: number }> = [];
  while ((controller = controllerRe.exec(src))) {
    controllers.push({ prefix: controller[1] ?? controller[2] ?? "", index: controller.index });
  }
  if (controllers.length === 0) return routes;
  let method: RegExpExecArray | null;
  while ((method = methodRe.exec(src))) {
    const verb = method[1]!.toUpperCase();
    const path = method[2] ?? method[3] ?? "";
    let prefix = controllers[0]!.prefix;
    for (const c of controllers) {
      if (c.index < method.index) prefix = c.prefix;
    }
    const full = `/${[prefix, path].filter(Boolean).join("/")}`.replace(/\/+/g, "/");
    routes.push(`${verb} ${full}`);
  }
  return routes;
}

describe("HTTP isolation catalog", () => {
  it("classifies every controller route", () => {
    const files = walk(join(__dirname));
    const found = new Set<string>();
    for (const file of files) {
      if (file.includes("isolation")) continue;
      for (const route of extractRoutes(readFileSync(file, "utf8"))) found.add(route);
    }
    const catalog = new Set(Object.keys(HTTP_ROUTE_GATES));
    const missing = [...found].filter((r) => !catalog.has(r)).sort();
    const extra = [...catalog].filter((r) => !found.has(r)).sort();
    expect({ missing, extra }).toEqual({ missing: [], extra: [] });
  });
});
