import express, { Request } from 'express';
import path from 'path';
import webpack from 'webpack';
const webpackDevMiddleware = require('webpack-dev-middleware');
import webpackHotMiddleware from 'webpack-hot-middleware';
import { tachyonFact } from 'tachyon-facts';

import { respondThroughCallback } from 'mweb/server/responder';
import {
  Render,
  Renderer,
  CHANNEL_OFFLINE_ERROR_NAME,
  RenderLocation,
  OFFLINE_CHANNEL_REDIRECT_VALUE,
} from 'mweb/server/renderer';
import configureStore from 'mweb/common/stores/configureStore';
import { ROUTES } from 'mweb/common/routes';
import { Location } from 'mweb/common/reducers/app';
import { buildChannelPath } from 'mweb/common/utils/pathBuilders';
import { ExperimentOverrideMapping } from 'mweb/common/actions/experiments';
import { parseExperimentOverrides } from 'mweb/lambda/experiment-override';

const webpackConfig = require(`mconfig/webpack.client.${process.env
  .MIMIC_PRODUCTION
  ? 'prod'
  : 'dev'}`);
const PORT = 3003;
const STARTED_MESSAGE = `Listening on ${PORT}. Navigate to http://localhost.twitch.tv:${PORT}`;
const BUCKET_ID = 'dev_bucket';

console.log(
  `Please enjoy this wonderful fact about tachyons while the server starts up: ${tachyonFact()}`,
);
const app = express();
const wp = webpack(webpackConfig);
const wpDev = webpackDevMiddleware(wp, {
  publicPath: webpackConfig.output.publicPath,
});

const buildRenderer = (
  acceptLanguageHeader: string | undefined,
  urlPath: string,
  experimentOverrides?: ExperimentOverrideMapping,
) =>
  new Renderer({
    appTemplate: String(
      wpDev.fileSystem.readFileSync(path.join(__dirname, './index.html')),
    ),
    chatEmbedTemplate: String(
      wpDev.fileSystem.readFileSync(path.join(__dirname, './chatEmbed.html')),
    ),
    acceptLanguageHeader,
    recordedPath: urlPath,
    bucketId: BUCKET_ID,
    experimentOverrides,
  });

const DEBUG_ROUTES: typeof ROUTES = [
  { route: '/upsell', location: Location.Upsell },
];

app.use('/static', express.static('static'));
app.use(wpDev);
app.use(webpackHotMiddleware(wp as any)); // necessary until wp hot middleware types get updated

[...DEBUG_ROUTES, ...ROUTES].forEach(({ route, location }) => {
  app.get(route, async (req, res) => {
    try {
      const responseHandler = buildResponseHandler(res);
      const renderer = buildRenderer(
        req.header('accept-language'),
        route,
        parseExperimentOverrides(req.param('experiment_overrides')),
      );
      const render = await renderer.render({
        channel: (req.param('channel') || '').toLowerCase(),
        theme: req.param('theme'),
        fontSize: req.param('fontSize'),
        gameAlias: req.param('gameAlias'),
        eventID: req.param('eventID'),
        vodID: req.param('vodID'),
        redirectedFrom: req.param('desktop-redirect'),
        location,
      } as RenderLocation);
      responseHandler(render);
    } catch (error) {
      if (error.name === CHANNEL_OFFLINE_ERROR_NAME) {
        res.redirect(
          `${buildChannelPath(
            req.param('channel'),
          )}/profile?desktop-redirect=${OFFLINE_CHANNEL_REDIRECT_VALUE}`,
        );
      } else {
        throw error;
      }
    }
  });
});

// Error pages do not currently have locations, so they can't shoehorn into the normal flow.
app.get('/this/page/not/found', async (req, res) => {
  console.info('Generating a 404.');
  const responseHandler = buildResponseHandler(res);
  const renderer = buildRenderer(getAcceptLanguage(req), req.path);
  const render = await renderer.renderNotFound();
  responseHandler(render);
});

app.get('/this/page/is/an/error', async (req, res) => {
  console.info('Generating a 500.');
  const responseHandler = buildResponseHandler(res);
  const renderer = await buildRenderer(getAcceptLanguage(req), req.path);
  const render = await renderer.renderError(
    new Error('test error'),
    configureStore(),
  );
  responseHandler(render);
});

app.listen(PORT, (error: Error) => {
  if (error) {
    console.error(error);
  } else {
    console.info(STARTED_MESSAGE);
  }
});

function buildResponseHandler(
  response: express.Response,
): (render: Render) => void {
  return function handler(render: Render): void {
    // This replicates the API Gateway response integrations without handling status codes. If we want to do
    // status codes we'll need to map statuses back to error codes, which is doable but seems unnecessary.
    respondThroughCallback(
      (errorState, successState) =>
        response.send(
          errorState
            ? JSON.parse(errorState).html
            : successState.html || { html: 'Something is very wrong.' },
        ),
      render,
    );
  };
}

function getAcceptLanguage(req: Request): string | undefined {
  const acceptLanguage = req.headers['accept-language'];
  if (Array.isArray(acceptLanguage)) {
    return acceptLanguage[0];
  }
  return acceptLanguage;
}
