import {
  assignMemberAccessRoleHandler,
  clearMemberAccessHandler,
  createMemberAccessRoleHandler,
  deleteMemberAccessRoleHandler,
  getMemberAccessAssignmentsHandler,
  getMemberAccessRoleHandler,
  getMyMemberAccessHandler,
  listMemberAccessHandler,
  listMemberAccessRolesHandler,
  unassignMemberAccessRoleHandler,
  updateMemberAccessRoleHandler,
} from "@/controllers/admin/member-access/member-access.controller";
import {
  getAccountByMembershipNumberHandler,
  getAccountOverviewHandler,
  getContactsByMembershipNumberHandler,
  getCuratedBookingsHandler,
  getMemberActivityLogsHandler,
  getMemberByMemberNumberHandler,
  getMemberPointsHandler,
  getMemberSavedSearchesHandler,
  getMemberSessionLogsHandler,
  getMemberSessionsHandler,
  getMembershipAnalyticsHandler,
  getMembershipBookingsHandler,
  getMembershipFilterOptionsHandler,
  listMembershipsHandler,
  syncMemberProfileHandler,
  updateMemberPasswordHandler,
} from "@/controllers/admin/members/members.controller";
import { UpdateMemberPasswordSchema } from "@/internal/members/member.schema";
import { sanitizePayload } from "@/internal/middlewares/sanitzePayload";
import { withConsoleUserRole } from "@/middlewares/console-role";
import { redisCacheMiddleware } from "@/middlewares/redis-cache.middleware";
import { withConsoleUserSession } from "@/middlewares/withConsoleUserSession";
import { withMemberAccess } from "@/middlewares/withMemberAccess";
import { Hono } from "hono";

export const MembersRoutes = new Hono();

const session = withConsoleUserSession();

/*
 * Role management grants the `members` module and nothing finer. What a user may
 * reach inside it — booking history, login history, the audit trail, saved
 * searches, aggregates, points, credential resets, Viewpoint syncs — plus which
 * slice of the member base they may see, comes from
 * `console_user_member_access`. Super admins bypass both layers.
 */
const canRead = withConsoleUserRole("members", "read");

MembersRoutes.get("/memberships", session, canRead, listMembershipsHandler);

// Whole-table grouped scans over the member DB, so cached. `?refresh=true`
// bypasses the cache when a fresh read is wanted.
MembersRoutes.get(
  "/analytics",
  session,
  canRead,
  withMemberAccess("can_view_analytics"),
  /*
   * Keyed by console user as well as by URL.
   *
   * The response now depends on the caller's access slice, not just the query
   * string — the default path-only key would have served one user's scoped
   * aggregates to another. `?refresh=true` still bypasses.
   */
  redisCacheMiddleware({
    ttl: 900,
    key: (ctx) => {
      const url = new URL(ctx.req.url);
      return `members:analytics:${ctx.get("consoleUserId") ?? "anon"}:${url.search}`;
    },
  }),
  getMembershipAnalyticsHandler,
);

MembersRoutes.get(
  "/filter-options",
  session,
  canRead,
  redisCacheMiddleware({ ttl: 3600 }),
  getMembershipFilterOptionsHandler,
);

MembersRoutes.get(
  "/account-overview/:membershipNumber",
  session,
  canRead,
  withMemberAccess("can_view_analytics"),
  redisCacheMiddleware({ ttl: 120 }),
  getAccountOverviewHandler,
);

// Live figure, so no cache.
MembersRoutes.get(
  "/points/:membershipNumber",
  session,
  canRead,
  withMemberAccess("can_view_points"),
  getMemberPointsHandler,
);

MembersRoutes.get(
  "/account/:membershipNumber",
  session,
  canRead,
  getAccountByMembershipNumberHandler,
);

MembersRoutes.get(
  "/contacts/:membershipNumber",
  session,
  canRead,
  getContactsByMembershipNumberHandler,
);

MembersRoutes.get(
  "/sessions/:userId",
  session,
  canRead,
  withMemberAccess("can_view_sessions"),
  getMemberSessionsHandler,
);

MembersRoutes.get(
  "/session-logs/:memberRecordId",
  session,
  canRead,
  withMemberAccess("can_view_sessions"),
  getMemberSessionLogsHandler,
);

MembersRoutes.get(
  "/saved-searches/:memberRecordId",
  session,
  canRead,
  withMemberAccess("can_view_saved_searches"),
  getMemberSavedSearchesHandler,
);

MembersRoutes.get(
  "/logs/:memberRecordId",
  session,
  canRead,
  withMemberAccess("can_view_activity_logs"),
  getMemberActivityLogsHandler,
);

// ── Super-admin only ────────────────────────────────────────────────────────
// Credential resets, Viewpoint profile syncs, and booking history (destination,
// external RCI, curated events).
MembersRoutes.patch(
  "/password/:memberRecordId",
  session,
  canRead,
  withMemberAccess("can_reset_password"),
  sanitizePayload(UpdateMemberPasswordSchema),
  updateMemberPasswordHandler,
);

MembersRoutes.put(
  "/sync-profile/:profileID",
  session,
  canRead,
  withMemberAccess("can_sync_profile"),
  syncMemberProfileHandler,
);

// Booking history is grantable via `member-bookings`; super admins bypass the
// role check inside the middleware, so they keep access either way.
MembersRoutes.get(
  "/bookings/:membershipNumber",
  session,
  canRead,
  withMemberAccess("can_view_bookings"),
  getMembershipBookingsHandler,
);

MembersRoutes.get(
  "/curated-bookings",
  session,
  canRead,
  withMemberAccess("can_view_bookings"),
  getCuratedBookingsHandler,
);

// ── Member access administration ────────────────────────────────────────────
/*
 * Gated on `member-access-list`, not `role-management` — handing out member data
 * access is a different job from granting console features, same split promo
 * access roles use. Super admins bypass the role check as always.
 *
 * Static segments are declared before "/:consoleUserId" and "/:id" so they are
 * not captured as ids.
 */
const canManageAccess = withConsoleUserRole("member-access-list", "read");
const canGrantAccess = withConsoleUserRole("member-access-list", "update");
const canCreateRoles = withConsoleUserRole("member-access-list", "create");
const canDeleteRoles = withConsoleUserRole("member-access-list", "delete");

MembersRoutes.get("/access/me", session, canRead, getMyMemberAccessHandler);

MembersRoutes.get(
  "/access/roles",
  session,
  canManageAccess,
  listMemberAccessRolesHandler,
);

MembersRoutes.post(
  "/access/roles",
  session,
  canCreateRoles,
  createMemberAccessRoleHandler,
);

MembersRoutes.get(
  "/access/roles/:id",
  session,
  canManageAccess,
  getMemberAccessRoleHandler,
);

MembersRoutes.put(
  "/access/roles/:id",
  session,
  canGrantAccess,
  updateMemberAccessRoleHandler,
);

MembersRoutes.delete(
  "/access/roles/:id",
  session,
  canDeleteRoles,
  deleteMemberAccessRoleHandler,
);

// Mapping is assignment only: a user holds roles, and their effective access is
// the union. There is no per-user capability editing — a copied grant went stale
// the moment its role changed, and could not represent two roles at once.
MembersRoutes.post(
  "/access/assign/:consoleUserId",
  session,
  canGrantAccess,
  assignMemberAccessRoleHandler,
);

MembersRoutes.delete(
  "/access/assign/:consoleUserId/:roleId",
  session,
  canGrantAccess,
  unassignMemberAccessRoleHandler,
);

MembersRoutes.get("/access", session, canManageAccess, listMemberAccessHandler);

MembersRoutes.get(
  "/access/:consoleUserId",
  session,
  canManageAccess,
  getMemberAccessAssignmentsHandler,
);

MembersRoutes.delete(
  "/access/:consoleUserId",
  session,
  canGrantAccess,
  clearMemberAccessHandler,
);

// Keep last: a bare `:memberNumber` would otherwise swallow the paths above.
MembersRoutes.get(
  "/:memberNumber",
  session,
  canRead,
  getMemberByMemberNumberHandler,
);
