import 'express-async-errors';

import path from 'path';

import { fallback } from '@blocklet/sdk/lib/middlewares/fallback';
import cookieParser from 'cookie-parser';
import cors from 'cors';
import dotenv from 'dotenv-flow';
import express, { ErrorRequestHandler } from 'express';
// eslint-disable-next-line import/no-extraneous-dependencies
import { CustomError, formatError, getStatusFromError } from '@blocklet/error';
import { csrf } from '@blocklet/sdk/lib/middlewares';
import { cdn } from '@blocklet/sdk/lib/middlewares/cdn';
import { xss } from '@blocklet/xss';

import { syncCurrencyLogo } from './crons/currency';
import crons from './crons/index';
import { ensureStakedForGas } from './integrations/arcblock/stake';
import { initResourceHandler } from './integrations/blocklet/resource';
import { initUserHandler } from './integrations/blocklet/user';
import { ensureWebhookRegistered } from './integrations/stripe/setup';
import { handlers } from './libs/auth';
import logger, { setupAccessLogger } from './libs/logger';
import { contextMiddleware, ensureI18n } from './libs/middleware';
import { ensureCreateOverdraftProtectionPrices } from './libs/overdraft-protection';
import { initEventBroadcast } from './libs/ws';
import { startCheckoutSessionQueue } from './queues/checkout-session';
import { startCreditConsumeQueue } from './queues/credit-consume';
import { startCreditGrantQueue } from './queues/credit-grant';
import { startReconciliationQueue } from './queues/credit-reconciliation';
import { startDiscountStatusQueue } from './queues/discount-status';
import { startEventQueue } from './queues/event';
import { startExchangeRateHealthQueue } from './queues/exchange-rate-health';
import { startInvoiceQueue } from './queues/invoice';
import { startNotificationQueue } from './queues/notification';
import { startPaymentQueue } from './queues/payment';
import { startPayoutQueue } from './queues/payout';
import { startRefundQueue } from './queues/refund';
import { startUploadBillingInfoListener } from './queues/space';
import { startSubscriptionQueue } from './queues/subscription';
import { startTokenTransferQueue } from './queues/token-transfer';
import { startVendorCommissionQueue } from './queues/vendors/commission';
import { startVendorFulfillmentQueue } from './queues/vendors/fulfillment';
import { startCoordinatedFulfillmentQueue } from './queues/vendors/fulfillment-coordinator';
import routes from './routes';
import autoRechargeAuthorizationHandlers from './routes/connect/auto-recharge-auth';
import changePaymentHandlers from './routes/connect/change-payment';
import changePlanHandlers from './routes/connect/change-plan';
import collectHandlers from './routes/connect/collect';
import collectBatchHandlers from './routes/connect/collect-batch';
import delegationHandlers from './routes/connect/delegation';
import overdraftProtectionHandlers from './routes/connect/overdraft-protection';
import payHandlers from './routes/connect/pay';
import reStakeHandlers from './routes/connect/re-stake';
import rechargeHandlers from './routes/connect/recharge';
import rechargeAccountHandlers from './routes/connect/recharge-account';
import setupHandlers from './routes/connect/setup';
import subscribeHandlers from './routes/connect/subscribe';
import changePayerHandlers from './routes/connect/change-payer';
import { initialize } from './store/models';
import { sequelize } from './store/sequelize';

dotenv.config();

initialize(sequelize);

export const app = express();
setupAccessLogger(app);
app.set('trust proxy', true);
app.use(cookieParser());
app.use((req, res, next) => {
  if (req.originalUrl.startsWith('/api/integrations/stripe/webhook')) {
    next();
  } else {
    express.json({ limit: '1 mb' })(req, res, next);
  }
});
app.use(express.urlencoded({ extended: true, limit: '1 mb' }));
app.use(cors());
app.use(xss({ allowedKeys: [] }));
app.use(csrf());
app.use(ensureI18n());
app.use(cdn());

const router = express.Router();
handlers.attach(Object.assign({ app: router }, collectHandlers));
handlers.attach(Object.assign({ app: router }, collectBatchHandlers));
handlers.attach(Object.assign({ app: router }, payHandlers));
handlers.attach(Object.assign({ app: router }, setupHandlers));
handlers.attach(Object.assign({ app: router }, subscribeHandlers));
handlers.attach(Object.assign({ app: router }, changePaymentHandlers));
handlers.attach(Object.assign({ app: router }, changePlanHandlers));
handlers.attach(Object.assign({ app: router }, rechargeHandlers));
handlers.attach(Object.assign({ app: router }, rechargeAccountHandlers));
handlers.attach(Object.assign({ app: router }, delegationHandlers));
handlers.attach(Object.assign({ app: router }, overdraftProtectionHandlers));
handlers.attach(Object.assign({ app: router }, reStakeHandlers));
handlers.attach(Object.assign({ app: router }, autoRechargeAuthorizationHandlers));
handlers.attach(Object.assign({ app: router }, changePayerHandlers));
router.use('/api', routes);

const isProduction = process.env.BLOCKLET_MODE === 'production';

app.use(contextMiddleware);
app.use(router);

if (isProduction) {
  const staticDir = path.resolve(process.env.BLOCKLET_APP_DIR!, 'dist');
  app.use(express.static(staticDir, { maxAge: '30d', index: false }));
  app.use(fallback('index.html', { root: staticDir }));
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
app.use(<ErrorRequestHandler>((err, req, res, _next) => {
  logger.error('handle router error', err);
  if (err instanceof CustomError) {
    res.status(getStatusFromError(err)).json({ error: formatError(err) });
    return;
  }
  if (req.accepts('json')) {
    res.status(500).send({ error: err.message });
  } else {
    res.status(500).send('Something broke!');
  }
}));

const port = parseInt(process.env.BLOCKLET_PORT!, 10);

export const server = app.listen(port, (err?: any) => {
  if (err) throw err;
  logger.info(`> payment-kit ready on ${port}`);

  syncCurrencyLogo();
  ensureCreateOverdraftProtectionPrices();
  startPaymentQueue().then(() => logger.info('payment queue started'));
  startInvoiceQueue().then(() => logger.info('invoice queue started'));
  startSubscriptionQueue().then(() => logger.info('subscription queue started'));
  startEventQueue().then(() => logger.info('event queue started'));
  startPayoutQueue().then(() => logger.info('payout queue started'));
  startVendorCommissionQueue().then(() => logger.info('vendor commission queue started'));
  startVendorFulfillmentQueue().then(() => logger.info('vendor fulfillment queue started'));
  startCoordinatedFulfillmentQueue().then(() => logger.info('coordinated fulfillment queue started'));
  startCheckoutSessionQueue().then(() => logger.info('checkoutSession queue started'));
  startNotificationQueue().then(() => logger.info('notification queue started'));
  startRefundQueue().then(() => logger.info('refund queue started'));
  startCreditConsumeQueue().then(() => logger.info('credit queue started'));
  startCreditGrantQueue().then(() => logger.info('credit grant queue started'));
  startTokenTransferQueue().then(() => logger.info('token transfer queue started'));
  startReconciliationQueue().then(() => logger.info('credit reconciliation queue started'));
  startDiscountStatusQueue().then(() => logger.info('discount status queue started'));
  startExchangeRateHealthQueue();
  logger.info('exchange rate health queue started');
  startUploadBillingInfoListener();

  if (process.env.BLOCKLET_MODE === 'production') {
    ensureWebhookRegistered().catch(console.error);
  }

  ensureStakedForGas().catch(console.error);

  crons.init();

  initEventBroadcast();

  initResourceHandler();

  initUserHandler();
});
