import {
  attachPromoCodesHandler,
  bulkDetachPromoCodesHandler,
  campaignAnalyticsHandler,
  campaignLogsHandler,
  campaignSignupsHandler,
  codeCampaignMapHandler,
  createCampaignHandler,
  memberReferralAnalyticsHandler,
  memberPointsHandler,
  reportFilterOptionsHandler,
  reportUsersHandler,
  dashboardOverviewHandler,
  deleteCampaignHandler,
  detachPromoCodeHandler,
  getCampaignHandler,
  listCampaignPromoCodesHandler,
  listCampaignsHandler,
  listCountriesHandler,
  campaignPromoCodeMapHandler,
  campaignsForPromoCodeHandler,
  promoCodeAnalyticsHandler,
  promoCodesAnalyticsHandler,
  listAllCampaignsForAccessListHandler,
  listAllKnownPromoCodesHandler,
  promoCodeUsersHandler,
  reconcileOrphanedPromoCodesHandler,
  signupAggregateHandler,
  signupsSummaryHandler,
  updateCampaignHandler,
  promoCodeBookingsHandler,
  promoCodeBookingsAnalyticsHandler,
} from "@/controllers/admin/promo-code-campaigns/promo-code-campaigns.controller";
import {
  sanitizePayload,
  sanitizeQueryParams,
} from "@/internal/middlewares/sanitzePayload";
import { withConsoleUserRole } from "@/middlewares/console-role";
import { withConsoleUserSession } from "@/middlewares/withConsoleUserSession";
import {
  AnalyticsQuerySchema,
  AttachPromoCodesSchema,
  BulkDetachPromoCodesSchema,
  CampaignSignupsSchema,
  CreateCampaignSchema,
  ListCampaignsQuerySchema,
  ReportFilterOptionsSchema,
  ReportUsersSchema,
  SignupAggregateSchema,
  SignupsSummarySchema,
  UpdateCampaignSchema,
  UsersByPromoCodeQuerySchema,
} from "@/v1/services/admin/promo-code-campaigns/promo-code-campaigns.schema";
import { Hono } from "hono";
import { redisCacheMiddleware } from "@/middlewares/redis-cache.middleware";

export const PromoCodeCampaignsRoutes = new Hono();

PromoCodeCampaignsRoutes.get(
  "/dashboard",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  dashboardOverviewHandler,
);

// Aggregated analytics across ALL member-referral codes (no campaign row).
// Declared before "/:id" so the static segment is not captured as a campaign id.
PromoCodeCampaignsRoutes.get(
  "/member-referrals/analytics",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizeQueryParams(AnalyticsQuerySchema),
  redisCacheMiddleware({ ttl: 300 }),
  memberReferralAnalyticsHandler,
);

// Aggregated analytics across ALL regular (non-member-referral) promo codes.
// Declared before "/:id" so the static "promo-codes" segment is not captured as
// a campaign id (otherwise "/:id/analytics" matches with id="promo-codes").
PromoCodeCampaignsRoutes.get(
  "/promo-codes/analytics",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizeQueryParams(AnalyticsQuerySchema),
  redisCacheMiddleware({ ttl: 300 }),
  promoCodesAnalyticsHandler,
);

// Promocode Access List picker — live Updot codes + historical codes that
// were attached to a campaign locally but have since been deleted in Updot.
// Declared before "/:id" so this static segment isn't captured as a campaign id.
PromoCodeCampaignsRoutes.get(
  "/all-known-promo-codes",
  withConsoleUserSession(),
  withConsoleUserRole("promocode-access-list", "read"),
  listAllKnownPromoCodesHandler,
);

// Unfiltered campaign→promo-code mapping for the Access List picker, so
// checking a campaign can auto-grant its promo codes. Declared before "/:id"
// for the same reason as the routes above.
PromoCodeCampaignsRoutes.get(
  "/campaign-promo-code-map",
  withConsoleUserSession(),
  withConsoleUserRole("promocode-access-list", "read"),
  campaignPromoCodeMapHandler,
);

// Unfiltered campaign list for the Access List picker (see handler comment
// for why this can't reuse the regular "/" campaigns list).
PromoCodeCampaignsRoutes.get(
  "/all-campaigns",
  withConsoleUserSession(),
  withConsoleUserRole("promocode-access-list", "read"),
  listAllCampaignsForAccessListHandler,
);

// Maintenance — detach promo_code_campaign_map rows whose promo code was
// deleted upstream in Updot. Manual/on-demand only (not scheduled). POST
// doesn't collide with "/:id" (only GET/PATCH/DELETE use that pattern), but
// kept here alongside the other pre-"/:id" static routes for consistency.
PromoCodeCampaignsRoutes.post(
  "/reconcile-orphaned-codes",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "delete"),
  reconcileOrphanedPromoCodesHandler,
);

PromoCodeCampaignsRoutes.get(
  "/",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizeQueryParams(ListCampaignsQuerySchema),
  listCampaignsHandler,
);

PromoCodeCampaignsRoutes.post(
  "/",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "create"),
  sanitizePayload(CreateCampaignSchema),
  createCampaignHandler,
);

PromoCodeCampaignsRoutes.get(
  "/:id",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  getCampaignHandler,
);

PromoCodeCampaignsRoutes.patch(
  "/:id",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "update"),
  sanitizePayload(UpdateCampaignSchema),
  updateCampaignHandler,
);

PromoCodeCampaignsRoutes.delete(
  "/:id",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "delete"),
  deleteCampaignHandler,
);

PromoCodeCampaignsRoutes.get(
  "/:id/promo-codes",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  listCampaignPromoCodesHandler,
);

PromoCodeCampaignsRoutes.post(
  "/:id/promo-codes",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "update"),
  sanitizePayload(AttachPromoCodesSchema),
  attachPromoCodesHandler,
);

// Bulk detach (body: { promo_code_ids }). Declared before the single-code
// route so it matches /:id/promo-codes without a trailing :promoCodeId.
PromoCodeCampaignsRoutes.delete(
  "/:id/promo-codes",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "update"),
  sanitizePayload(BulkDetachPromoCodesSchema),
  bulkDetachPromoCodesHandler,
);

PromoCodeCampaignsRoutes.delete(
  "/:id/promo-codes/:promoCodeId",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "update"),
  detachPromoCodeHandler,
);

PromoCodeCampaignsRoutes.get(
  "/:id/analytics",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizeQueryParams(AnalyticsQuerySchema),
  redisCacheMiddleware({ ttl: 300 }),
  campaignAnalyticsHandler,
);

// Campaign change history — gated by global admin-logs:read rather than campaign read.
PromoCodeCampaignsRoutes.get(
  "/:id/logs",
  withConsoleUserSession(),
  withConsoleUserRole("admin-logs" as any, "read"),
  campaignLogsHandler,
);

PromoCodeCampaignsRoutes.get(
  "/promo-codes/:promoCodeId/analytics",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizeQueryParams(AnalyticsQuerySchema),
  redisCacheMiddleware({ ttl: 300 }),
  promoCodeAnalyticsHandler,
);

PromoCodeCampaignsRoutes.get(
  "/by-code/:code/campaigns",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  campaignsForPromoCodeHandler,
);

PromoCodeCampaignsRoutes.post(
  "/promo-codes/signups-summary",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizePayload(SignupsSummarySchema),
  signupsSummaryHandler,
);

// Flexible Reports — combined user list across one or more promo codes.
PromoCodeCampaignsRoutes.post(
  "/reports/users",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizePayload(ReportUsersSchema),
  reportUsersHandler,
);

PromoCodeCampaignsRoutes.post(
  "/reports/filter-options",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizePayload(ReportFilterOptionsSchema),
  reportFilterOptionsHandler,
);

// All countries from the DB `countries` table — used by the Campaigns listing
// page to populate the CountryMultiSelect from the DB.
PromoCodeCampaignsRoutes.get(
  "/reports/countries",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  listCountriesHandler,
);

// Reports — per-code, per-country signup aggregate for a window.
PromoCodeCampaignsRoutes.post(
  "/reports/signup-aggregate",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizePayload(SignupAggregateSchema),
  signupAggregateHandler,
);

// Reports — per-campaign signup totals for a window (+ optional country).
PromoCodeCampaignsRoutes.post(
  "/reports/campaign-signups",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizePayload(CampaignSignupsSchema),
  campaignSignupsHandler,
);

// Reports — full code→campaign mapping for the Members report's Campaign
// filter + column (Updot members joined to OUR campaigns by promo code).
PromoCodeCampaignsRoutes.get(
  "/reports/code-campaign-map",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  codeCampaignMapHandler,
);

// The bookings behind a Bookings Analytics slice. POST because the dashboard
// drills in across every code in scope, which is too many for a query string.
// Chart aggregates for the bookings page. Server-side so the charts cover every
// booking rather than whatever page the table happens to have fetched.
PromoCodeCampaignsRoutes.post(
  "/bookings/analytics",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  promoCodeBookingsAnalyticsHandler,
);

PromoCodeCampaignsRoutes.post(
  "/bookings",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  promoCodeBookingsHandler,
);

PromoCodeCampaignsRoutes.get(
  "/promo-codes/:promoCodeId/users",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  sanitizeQueryParams(UsersByPromoCodeQuerySchema),
  promoCodeUsersHandler,
);

PromoCodeCampaignsRoutes.get(
  "/members/:membershipNumber/points",
  withConsoleUserSession(),
  withConsoleUserRole("promo-code-campaigns", "read"),
  memberPointsHandler,
);
