import bodyParser from 'body-parser';
import express from 'express';
import { composerMessageHandler } from './controllers/composer.js';
import { databaseAndRedisHealthCheck, healthCheckReachable } from './controllers/healthcheck.js';
import { installMessageHandlerWithShutdownSemaphore } from './utils/platformEventHandling.js';

import { logFrontendActivity } from './controllers/activity.js';
import {
  addPlanOverride,
  disablePlanOverride,
  enablePlanOverride,
  getCancellationData,
  getCancellations,
  getPlanOverrides,
  getPlans,
  getUserActivity,
  getUserComposerRequests,
  getUserConductorRequests,
  getUserForAdminUse,
  getUserProjects,
  getUserSharedTracks,
  searchUsers,
  updatePlanOverrideNote,
} from './controllers/admin.js';
import {
  activeSubsHandler,
  churnHandler,
  moneyInHandler,
  mrrHandler,
  trialConversionHandler,
  userSignupHandler,
} from './controllers/adminDashboards.js';
import {
  archiveCloudSave,
  cloudSaveProject,
  deleteCoudSave,
  favouriteCloudSave,
  getCloudSavedProjectByUuid,
  getCloudSavedProjectHistory,
  getCloudSavedProjectVersion,
  getCloudSavedProjects,
  getProjectRemixable,
  getRemixableProjectDetails,
  remixProject,
  restoreCloudSave,
  setProjectRemixable,
  unarchiveCloudSave,
  unfavouriteCloudSave,
} from './controllers/cloudSave.js';
import {
  conductorMessageHandler,
  getConductorLimits,
  getConductorStatusMessage,
  getSemanticRank,
} from './controllers/conductor.js';
import { getConductorPresetMessage } from './controllers/conductorPresetMessages.js';
import { demucsMessageHandler } from './controllers/demucs.js';
import {
  getDeviceByPreset,
  getDeviceByUuid,
  getDevices,
  getPresetByUuid,
  getPresetForTrack,
  getPresetSettings,
  getPresetsForDevice,
  saveDevicePreset,
  searchDevices,
} from './controllers/devices.js';
import { downbeatsMessageHandler } from './controllers/downbeatsMessageHandler.js';
import { sendLoginEmail, sendMobileCouponEmail } from './controllers/emails.js';
import { getPlansWithFeatures } from './controllers/features.js';
import { saveFeedback } from './controllers/feedback.js';
import { lalalaMessageHandler } from './controllers/lalala.js';
import { vstSignup } from './controllers/landingPages.js';
import { selfServeAddPlanOverride } from './controllers/planOverrides.js';
import {
  createQuestion,
  getAnswer,
  getQuestion,
  getQuestions,
  updateAnswer,
  updateQuestion,
} from './controllers/qa.js';
import { getQuickstartMetadata, quickstartMessageHandler, uploadAnonymousSample } from './controllers/quickstart.js';
import { restartOnboarding } from './controllers/restartOnboarding.js';
import {
  acceptGeneratedSample,
  addTagToSamplesInPack,
  createSamplePack,
  deleteSampleTag,
  finishSampleUpload,
  generateSample,
  getGeneratedSamplesByUserId,
  getMySamples,
  getSampleData,
  getSamplePackByUuid,
  getSamplePacks,
  getSamplePermitted,
  getSampleUploadPartURL,
  getSampleUrlByUuid,
  getSampleWaveformByUuid,
  rejectGeneratedSample,
  searchSamples,
  startSampleUpload,
  updateSampleData,
} from './controllers/samples.js';
import {
  addTrackPreset,
  addTrackPresetTags,
  deleteTrackPresetTags,
  findTrackPresets,
  getAllTrackPresetCategories,
  getAllTrackPresetTags,
  getTrackPreset,
  getTrackPresetTags,
  getTrackPresets,
  updateTrackPreset,
  updateTrackPresetTags,
} from './controllers/trackPresets.js';
import {
  getSharedTracks,
  getTrack,
  getTrackPlays,
  handleFileSharingUpload,
  setSharedTrackName,
  setSharingUsername,
} from './controllers/trackSharing.js';
import uploadVideo from './controllers/uploadVideo.js';
import { generateShortUrl, getLongUrl, getShortUrlWithStats, getUserShortUrls } from './controllers/urlShortener.js';
import {
  getLanguage,
  getUserIdentities,
  requestDeletionHandler,
  requestPasswordResetHandler,
  setSharename,
  setUsername,
  updateLanguage,
  updateUserCategorisation,
} from './controllers/users.js';
import { videoDownloadRedirect } from './controllers/videoDownloadRedirect.js';
import {
  createVoiceConversionModelEndpoint,
  deleteVoiceConversionModel,
  getVoiceConversionModels,
  voiceConversionMessageHandler,
} from './controllers/voiceConversion.js';
import {
  generateVoiceConversionScript,
  startTrainingModelFromScript,
  verifyVoiceConversionScript,
} from './controllers/voiceConversionVerification.js';
import {
  getUserStandup,
  getWeekStandupHandler,
  getWeekStandupSummary,
  summariseWeekStandupsHandler,
  upsertUserStandup,
} from './controllers/wavtoolStandups.js';
import { handleCatchUnusedWebhook } from './controllers/webhooks.js';
import { createWelcomeSurveyResponse, getWelcomeSurveyResponse } from './controllers/welcomeSurvey.js';
import {
  getLast10ZohoTokens,
  getLatestEngagementScoreSync,
  getRecentSyncs,
  getRecentUserSyncs,
  getSyncData,
  getZohoAccessToken,
  refreshLatestZohoTokenEndpoint,
  syncEngagementScoresToZoho,
  syncUserToZohoEndpoint,
} from './controllers/zohoSync.js';
import {
  handleCancellationReasons,
  handlePaddleWebhook,
  paddleCancelSubscription,
  paddleCheckoutSession,
  paddleUncancelSubscription,
  paddleUpdateSubscription,
} from './external-services/paddle.js';
import { handleSendgridWebhook } from './external-services/sendgrid.js';
import sentryTunnel from './external-services/sentryTunnel.js';
import {
  checkoutSessionFunction,
  handleStripeWebhook,
  portalSessionFunction,
} from './external-services/stripeIntegration.js';
import { typeformWebhook } from './external-services/typeform.js';
import { bind } from './server-utils/routesHandler.js';
import {
  bindPublicSocketService,
  bindSocketService,
  socketMessageHandler,
} from './server-utils/socketMessageHandler.js';
import { getTopEngagedUsers, getRecentEngagedUsers } from './controllers/adminEngagement.js';

function ensureAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  }
  res.status(403);
  res.send({ error: 'Not authenticated' });
}

function ensureAdmin(req, res, next) {
  if (req.isAuthenticated() && req.user.role === 'admin') {
    return next();
  }
  res.status(403);
  res.send({ error: 'Not authenticated' });
}

export default function routes(app, socketIO, appUri) {
  const apiRouter = express.Router();
  const apiPublicRouter = express.Router();
  const apiAdminRouter = express.Router();

  const webhookRouter = express.Router();
  const shortyRouter = express.Router();

  const shortyGet = shortyRouter.get.bind(shortyRouter);
  const shortyPost = shortyRouter.post.bind(shortyRouter);
  shortyRouter.use(bodyParser.json({ limit: '50mb' }));

  const apiPublicGet = apiPublicRouter.get.bind(apiPublicRouter);
  const apiPublicPost = apiPublicRouter.post.bind(apiPublicRouter);

  bind(apiPublicPost, '/tunnel', sentryTunnel);

  // note: do not parse stripe routes!
  // note: seriously that note is important ^
  // note: this broke production payments twice. if you make it three times, Srini will find you.
  apiPublicRouter.use(bodyParser.json({ limit: '50mb' }));

  bind(apiPublicGet, '/track/:trackId', getTrack);
  bind(apiPublicPost, '/track/add-play', getTrackPlays);
  bind(apiPublicPost, '/feedback', saveFeedback);
  bind(apiPublicGet, '/download/:userId/:fileId', videoDownloadRedirect);
  bind(apiPublicPost, '/vst-signup', vstSignup);
  bind(apiPublicGet, '/remixable/:projectId', getRemixableProjectDetails);
  bind(apiPublicPost, '/quickstart/metadata', getQuickstartMetadata);
  bindPublicSocketService('quickstart', quickstartMessageHandler);
  bind(apiPublicPost, '/quickstart/upload', uploadAnonymousSample);

  apiRouter.use(bodyParser.json({ limit: '50mb' }));
  apiRouter.use(ensureAuthenticated);

  const apiPost = apiRouter.post.bind(apiRouter);
  const apiGet = apiRouter.get.bind(apiRouter);
  const apiDelete = apiRouter.delete.bind(apiRouter);

  apiAdminRouter.use(bodyParser.json({ limit: '50mb' }));
  apiAdminRouter.use(ensureAdmin);

  const apiAdminPost = apiAdminRouter.post.bind(apiAdminRouter);
  const apiAdminGet = apiAdminRouter.get.bind(apiAdminRouter);

  bind(apiPost, '/send-login-email', sendLoginEmail);
  bind(apiPost, '/send-mobile-coupon-email', sendMobileCouponEmail);
  bind(apiPost, '/cancellation-feedback', handleCancellationReasons);

  // This is called act because calling it trackUserActivity would be too conspicuous.
  bind(apiPost, '/act', logFrontendActivity);

  // stripe routes (only for updating existing subscriptions)
  bind(apiPost, '/create-stripe-checkout-session', checkoutSessionFunction, { appUri });
  bind(apiPost, '/create-stripe-portal-session', portalSessionFunction, { appUri });

  // paddle routes
  bind(apiPost, '/create-paddle-checkout-session', paddleCheckoutSession);
  bind(apiPost, '/update-paddle-subscription', paddleUpdateSubscription);
  bind(apiGet, '/uncancel-paddle-subscription', paddleUncancelSubscription);
  bind(apiGet, '/cancel-paddle-subscription', paddleCancelSubscription);

  bind(apiPost, '/conductor-status-message', getConductorStatusMessage);

  bind(apiGet, '/conductor-preset-message/:id', getConductorPresetMessage);

  bind(apiPost, '/planOverride', selfServeAddPlanOverride);

  bind(apiGet, '/samples/packs/:uuid', getSamplePackByUuid);
  bind(apiGet, '/samples', searchSamples);
  bind(apiGet, '/samples/mine', getMySamples);
  bind(apiGet, '/samples/data/:uuid', getSampleData);
  bind(apiPost, '/samples/data/:uuid', updateSampleData);
  bind(apiPost, '/samples/packs/:uuid/tag', addTagToSamplesInPack);
  bind(apiPost, '/samples/data/:uuid/tag/delete', deleteSampleTag);
  bind(apiGet, '/samples/generated-samples', getGeneratedSamplesByUserId);
  bind(apiGet, '/samples/packs', getSamplePacks);
  bind(apiPost, '/samples/generate-sample', generateSample);
  bind(apiPost, '/samples/reject-generated-sample', rejectGeneratedSample);
  bind(apiPost, '/samples/accept-generated-sample', acceptGeneratedSample);
  bind(apiPost, '/samples/packs/create', createSamplePack);

  bind(apiPost, '/samples/upload', startSampleUpload);
  bind(apiPost, '/samples/upload-part', getSampleUploadPartURL);
  bind(apiPost, '/samples/uploaded', finishSampleUpload);
  bind(apiPost, '/samples/:uuid', getSampleUrlByUuid);
  bind(apiPost, '/samples/:uuid/waveform', getSampleWaveformByUuid);
  bind(apiGet, '/samples/:uuid/permitted', getSamplePermitted);

  bind(apiPost, '/upload-video', uploadVideo);

  bind(apiPost, '/upload-for-sharing', handleFileSharingUpload);
  bind(apiGet, '/shared-tracks', getSharedTracks);
  bind(apiPost, '/shared-track/:id/name', setSharedTrackName);
  bind(apiPost, '/sharing-username', setSharingUsername);

  bind(apiGet, '/conductorLimits', getConductorLimits);

  bind(apiPost, '/update-categorisation', updateUserCategorisation);
  bind(apiPost, '/user/set-language', updateLanguage);
  bind(apiGet, '/user/get-language', getLanguage);
  bind(apiGet, '/user/get-accounts', getUserIdentities);
  bind(apiPost, '/user/reset-password', requestPasswordResetHandler);
  bind(apiPost, '/user/delete-account', requestDeletionHandler);
  bind(apiPost, '/user/update-username', setUsername);
  bind(apiPost, '/user/update-sharename', setSharename);

  bind(apiPost, '/cloud-saves', cloudSaveProject);
  bind(apiPost, '/cloud-saves/remix', remixProject);
  bind(apiPost, '/cloud-saves/:uuid/set-remixable', setProjectRemixable);
  bind(apiGet, '/cloud-saves/:uuid/remixable', getProjectRemixable);
  bind(apiGet, '/cloud-saves', getCloudSavedProjects);
  bind(apiGet, '/cloud-saves/:uuid', getCloudSavedProjectByUuid);
  bind(apiGet, '/cloud-saves/:uuid/history', getCloudSavedProjectHistory);
  bind(apiGet, '/cloud-saves/:uuid/history/:version', getCloudSavedProjectVersion);
  bind(apiDelete, '/cloud-saves/:uuid', deleteCoudSave);
  bind(apiGet, '/cloud-saves/:uuid/enable', restoreCloudSave);
  bind(apiGet, '/cloud-saves/:uuid/archive', archiveCloudSave);
  bind(apiGet, '/cloud-saves/:uuid/unarchive', unarchiveCloudSave);
  bind(apiGet, '/cloud-saves/:uuid/favourite', favouriteCloudSave);
  bind(apiGet, '/cloud-saves/:uuid/unfavourite', unfavouriteCloudSave);

  // Voice Conversion (Prototypes)
  bind(apiGet, '/voice_conversion/models', getVoiceConversionModels);
  bind(apiPost, '/voice_conversion/models', createVoiceConversionModelEndpoint);
  bind(apiDelete, '/voice_conversion/models/:uuid', deleteVoiceConversionModel);

  // Voice Conversion Verification (Prototype Training Verification)
  bind(apiPost, '/voice_conversion/verify/generate-script', generateVoiceConversionScript);
  bind(apiPost, '/voice_conversion/verify/verify-script', verifyVoiceConversionScript);
  bind(apiPost, '/voice_conversion/verify/create-model', startTrainingModelFromScript);

  installMessageHandlerWithShutdownSemaphore(socketIO, socketMessageHandler);
  bindSocketService('conductor', conductorMessageHandler);
  bindSocketService('composer', composerMessageHandler);
  bindSocketService('downbeats', downbeatsMessageHandler);
  bindSocketService('demucs', demucsMessageHandler);
  bindSocketService('lalala', lalalaMessageHandler);
  // disabled for now (used via audiocraftGenerateAudio instead)
  //bindSocketService('audiocraft', audiocraftMessageHandler);
  bindSocketService('voice_conversion', voiceConversionMessageHandler);

  bind(apiPost, '/semanticRank', getSemanticRank);

  // Devices and presets
  bind(apiPost, '/devices', getDevices);
  bind(apiPost, '/devices/search', searchDevices);
  bind(apiPost, '/device/by-preset', getDeviceByPreset);
  bind(apiPost, '/device/:uuid', getDeviceByUuid);
  bind(apiPost, '/device/:uuid/presets', getPresetsForDevice);
  bind(apiPost, '/preset/for/:trackName', getPresetForTrack);
  bind(apiPost, '/device/:uuid/save-preset', saveDevicePreset);
  bind(apiPost, '/preset/:uuid/settings', getPresetSettings);
  bind(apiPost, '/preset/:uuid', getPresetByUuid);

  // Track Presets
  bind(apiGet, '/tracks/presets', getTrackPresets);
  bind(apiPost, '/tracks/presets', addTrackPreset);

  bind(apiGet, '/tracks/presets/:trackPresetId/tags', getTrackPresetTags);
  bind(apiPost, '/tracks/presets/:trackPresetId/tags', addTrackPresetTags);
  bind(apiPost, '/tracks/presets/:trackPresetId/tags/delete', deleteTrackPresetTags);
  bind(apiGet, '/tracks/presets/find/:query', findTrackPresets);
  bind(apiPost, '/tracks/presets/:trackPresetId/tags/update', updateTrackPresetTags);
  bind(apiGet, '/tracks/presets/tags', getAllTrackPresetTags);
  bind(apiGet, '/tracks/presets/categories', getAllTrackPresetCategories);
  bind(apiGet, '/tracks/presets/:trackPresetId', getTrackPreset);
  bind(apiPost, '/tracks/presets/:trackPresetId/update', updateTrackPreset);

  // Welcome Survey
  bind(apiGet, '/welcome-survey', getWelcomeSurveyResponse);
  bind(apiPost, '/welcome-survey', createWelcomeSurveyResponse);

  // Business Model
  bind(apiPost, '/features', getPlansWithFeatures);

  // Q&A manager
  bind(apiGet, '/qa/questions', getQuestions);
  bind(apiGet, '/qa/questions/:id', getQuestion);
  bind(apiPost, '/qa/questions/:id', updateQuestion);
  bind(apiPost, '/qa/answers/:id', updateAnswer);
  bind(apiGet, '/qa/answers/:id', getAnswer);
  bind(apiPost, '/qa/questions', createQuestion);

  // Admin api routes
  bind(apiAdminPost, '/admin/restartOnboarding', restartOnboarding);
  bind(apiAdminPost, '/admin/searchUsers', searchUsers);
  bind(apiAdminPost, '/admin/planOverrides', addPlanOverride);
  bind(apiAdminPost, '/admin/planOverride/:id/note', updatePlanOverrideNote);
  bind(apiAdminPost, '/admin/planOverride/:id/disable', disablePlanOverride);
  bind(apiAdminPost, '/admin/planOverride/:id/enable', enablePlanOverride);
  bind(apiAdminPost, '/admin/urlShorten', generateShortUrl);
  bind(apiAdminGet, '/admin/plans', getPlans);
  bind(apiAdminGet, '/admin/planOverrides', getPlanOverrides);
  bind(apiAdminGet, '/admin/user/scoresAllTime', getTopEngagedUsers);
  bind(apiAdminGet, '/admin/cancellations', getCancellations);
  bind(apiAdminGet, '/admin/cancellation-data', getCancellationData);
  bind(apiAdminGet, '/admin/user/scoresRecent', getRecentEngagedUsers);
  bind(apiAdminGet, '/admin/user/:userId', getUserForAdminUse);
  bind(apiAdminGet, '/admin/user/:userId/activity', getUserActivity);
  bind(apiAdminGet, '/admin/user/:userId/projects', getUserProjects);
  bind(apiAdminGet, '/admin/user/:userId/tracks', getUserSharedTracks);
  bind(apiAdminGet, '/admin/user/:userId/conductor', getUserConductorRequests);
  bind(apiAdminGet, '/admin/user/:userId/composer', getUserComposerRequests);
  bind(apiAdminGet, '/admin/shortUrls', getUserShortUrls);
  bind(apiAdminGet, '/admin/shortUrl/:shortCode', getShortUrlWithStats);

  bind(apiAdminGet, '/admin/dashboard/:date/signups/', userSignupHandler);
  bind(apiAdminGet, '/admin/dashboard/:date/activeSubs/', activeSubsHandler);
  bind(apiAdminGet, '/admin/dashboard/:date/trialConversion', trialConversionHandler);
  bind(apiAdminGet, '/admin/dashboard/:date/churn', churnHandler);
  bind(apiAdminGet, '/admin/dashboard/mrr', mrrHandler);
  bind(apiAdminGet, '/admin/dashboard/money', moneyInHandler);

  bind(apiAdminPost, '/admin/zoho/token', getZohoAccessToken);
  bind(apiAdminPost, '/admin/zoho/token/refresh', refreshLatestZohoTokenEndpoint);
  bind(apiAdminGet, '/admin/zoho/recentTokens', getLast10ZohoTokens);
  bind(apiAdminPost, '/admin/zoho/syncUser/:userId', syncUserToZohoEndpoint);
  bind(apiAdminGet, '/admin/zoho/recentSyncs', getRecentSyncs);
  bind(apiAdminGet, '/admin/zoho/recentUserSyncs/:userId', getRecentUserSyncs);
  bind(apiAdminGet, '/admin/zoho/syncStatus/:id', getSyncData);
  bind(apiAdminPost, '/admin/zoho/syncEngagementScore', syncEngagementScoresToZoho);
  bind(apiAdminGet, '/admin/zoho/latestEngagementScoreSync', getLatestEngagementScoreSync);

  bind(apiAdminGet, '/admin/standup/week/:date', getWeekStandupHandler);
  bind(apiAdminPost, '/admin/standup/summary/:date/generate', summariseWeekStandupsHandler);
  bind(apiAdminGet, '/admin/standup/summary/:date', getWeekStandupSummary);
  bind(apiAdminGet, '/admin/standup/:userId/:date', getUserStandup);
  bind(apiAdminPost, '/admin/standup', upsertUserStandup);

  const webhookPost = webhookRouter.post.bind(webhookRouter);

  bind(webhookPost, '/stripe', handleStripeWebhook);
  bind(webhookPost, '/typeform', typeformWebhook);
  bind(webhookPost, '/sendgrid', handleSendgridWebhook);
  bind(webhookPost, '/paddle', handlePaddleWebhook);
  bind(webhookPost, '/:endpoint', handleCatchUnusedWebhook);

  app.use('/api', apiPublicRouter);
  app.use('/api', apiRouter);
  app.use('/api', apiAdminRouter);
  app.use('/webhook', webhookRouter);

  const appGet = app.get.bind(app);

  bind(appGet, '/healthz', databaseAndRedisHealthCheck);
  bind(appGet, '/healthz-reachable', healthCheckReachable);

  bind(shortyGet, '/:id', getLongUrl);
  // if (process.env.NODE_ENV !== 'production') bind(shortyPost, '/shorten', generateShortUrl);

  app.use('/s', shortyRouter);
}
