import 'cross-fetch/polyfill';
import { readFileSync } from 'fs';
import { join } from 'path';

import { renderToStringWithData } from '@apollo/react-ssr';
import React from 'react';
import express, { Express } from 'express';
import compression from 'compression';
import { map, mergeMap } from 'rxjs/operators';
import { defer, Observable, of } from 'rxjs';
import ApolloClient from 'apollo-client';

import { gqlState } from './gqlState';

import { app, AppOutput, PluginFn } from './index';

// const stats = readFileSync(template, 'utf8');

export function initServer(el: React.ReactElement, plugins: PluginFn[] = [], before?: (exp: Express) => Express) {
    const template = join(__dirname, '..', 'browser', '_index.html');
    const html = readFileSync(template, 'utf8');
    const pages = JSON.parse(readFileSync(join(__dirname, '..', 'browser', 'pages.json'), 'utf8'));

    const exp = express();
    exp.use(compression());
    before && before(exp);
    exp.use(express.static(join(__dirname, '..', 'browser')));
    exp.use(function (req, res, next) {
        /**
         * Parse the user-agent to decide whether or not to support the
         * modern loading of JS
         */
        const match = req.url.match(/^\/(?:(.*)\.html)?/);
        const isAsset = req.url.match(/\.(map|ico)$/);

        if (!match || isAsset) {
            return next();
        }

        const [pathname, maybeSearch] = req.url.split('?');
        const search = maybeSearch ? '?' + maybeSearch : '';

        app(pathname, search, {}, pages, {}, el, plugins)
            .pipe(
                mergeMap(
                    (params): Observable<[string, AppOutput]> => {
                        switch (params.response.kind) {
                            case 'not-found':
                            case 'success': {
                                if (!app) {
                                    return of(['', params]);
                                }
                                return defer(() => renderToStringWithData(app as any)).pipe(
                                    map((html) => [html, params]),
                                );
                            }
                            default:
                                return of(['', params]);
                        }
                    },
                ),
            )
            .subscribe(([ssrHtml, { response, appContext, app }]) => {
                const client: ApolloClient<any> = appContext.client;
                switch (response.kind) {
                    case 'not-found':
                    case 'success': {
                        if (!app) {
                            res.status(500).send('Could not generate application');
                            return false;
                        }
                        const replaced = html
                            .replace(`<!--react-html-->`, ssrHtml)
                            .replace(`<!--gql-state-->`, gqlState(client.extract()));
                        if (response.kind === 'success') {
                            res.send(replaced);
                        }
                        if (response.kind === 'not-found') {
                            res.status(404).send(replaced);
                        }
                        break;
                    }
                    case 'error': {
                        res.status(response.statusCode).send(response.message);
                        break;
                    }
                    case 'redirect': {
                        res.redirect(response.location, response.statusCode);
                    }
                }
            });
    });
    return exp;
}
