'use strict';

module.exports.create = function (webserver, xconfx, state) {
  console.log('[worker] create');
  xconfx.debug = true;
  console.log('DEBUG create worker');

  if (!state) {
    state = {};
  }

  var PromiseA = state.Promise || require('bluebird');
  var memstore;
  var sqlstores = {};
  var systemFactory = require('sqlite3-cluster/client').createClientFactory({
      dirname: xconfx.varpath
    , prefix: 'walnut+'
    //, dbname: 'config'
    , suffix: '@daplie.com'
    , ext: '.sqlite3'
    , sock: xconfx.sqlite3Sock
    , ipcKey: xconfx.ipcKey
  });
  /*
  var clientFactory = require('sqlite3-cluster/client').createClientFactory({
      algorithm: 'aes'
    , bits: 128
    , mode: 'cbc'
    , dirname: xconfx.varpath // TODO conf
    , prefix: 'com.daplie.walnut.'
    //, dbname: 'cluster'
    , suffix: ''
    , ext: '.sqlcipher'
    , sock: xconfx.sqlite3Sock
    , ipcKey: xconfx.ipcKey
  });
  */
  var cstore = require('cluster-store');

  console.log('[worker] creating data stores...');
  return PromiseA.all([
    // TODO security on memstore
    // TODO memstoreFactory.create
    cstore.create({
      sock: xconfx.memstoreSock
    , connect: xconfx.memstoreSock
      // TODO implement
    , key: xconfx.ipcKey
    }).then(function (_memstore) {
      console.log('[worker] cstore created');
      memstore = PromiseA.promisifyAll(_memstore);
      return memstore;
    })
    // TODO mark a device as lost, stolen, missing in DNS records
    // (and in turn allow other devices to lock it, turn on location reporting, etc)
  , systemFactory.create({
        init: true
      , dbname: 'config'
    }).then(function (sysdb) {
      console.log('[worker] sysdb created');
      return sysdb;
    })
  ]).then(function (args) {
    console.log('[worker] database factories created');
    memstore = args[0];
    sqlstores.config = args[1];

    var wrap = require('masterquest-sqlite3');
    var dir = [
      { tablename: 'com_daplie_walnut_config'
      , idname: 'id'
      , unique: [ 'id' ]
      , indices: [ 'createdAt', 'updatedAt' ]
      }
    , { tablename: 'com_daplie_walnut_redirects'
      , idname: 'id'      // blog.example.com:sample.net/blog
      , unique: [ 'id' ]
      , indices: [ 'createdAt', 'updatedAt' ]
      }
    ];

    function scopeMemstore(expId) {
      var scope = expId + '|';
      return {
        getAsync: function (id) {
          return memstore.getAsync(scope + id);
        }
      , setAsync: function (id, data) {
          return memstore.setAsync(scope + id, data);
        }
      , touchAsync: function (id, data) {
          return memstore.touchAsync(scope + id, data);
        }
      , destroyAsync: function (id) {
          return memstore.destroyAsync(scope + id);
        }

      // helpers
      , allAsync: function () {
          return memstore.allAsync().then(function (db) {
            return Object.keys(db).filter(function (key) {
              return 0 === key.indexOf(scope);
            }).map(function (key) {
              return db[key];
            });
          });
        }
      , lengthAsync: function () {
          return memstore.allAsync().then(function (db) {
            return Object.keys(db).filter(function (key) {
              return 0 === key.indexOf(scope);
            }).length;
          });
        }
      , clearAsync: function () {
          return memstore.allAsync().then(function (db) {
            return Object.keys(db).filter(function (key) {
              return 0 === key.indexOf(scope);
            }).map(function (key) {
              return memstore.destroyAsync(key);
            });
          }).then(function () {
            return null;
          });
        }
      };
    }

    return wrap.wrap(sqlstores.config, dir).then(function (models) {
      console.log('[worker] database wrapped');
      return models.ComDaplieWalnutConfig.find(null, { limit: 100 }).then(function (results) {
        console.log('[worker] config query complete');
        return models.ComDaplieWalnutConfig.find(null, { limit: 10000 }).then(function (redirects) {
          console.log('[worker] configuring express');
          var express = require('express-lazy');
          var app = express();
          var recase = require('connect-recase')({
            // TODO allow explicit and or default flag
            explicit: false
          , default: 'snake'
          , prefixes: ['/api']
            // TODO allow exclude
          //, exclusions: [config.oauthPrefix]
          , exceptions: {}
          //, cancelParam: 'camel'
          });
          var bootstrapApp;
          var mainApp;
          var apiDeps = {
            models: models
            // TODO don't let packages use this directly
          , Promise: PromiseA
          , dns: PromiseA.promisifyAll(require('dns'))
          , crypto: PromiseA.promisifyAll(require('crypto'))
          , fs: PromiseA.promisifyAll(require('fs'))
          , path: require('path')
          , validate: {
              isEmail: function (email) {
                return /@/.test(email) && !/\s+/.test(email);
              }
            , email: function (email) {
                if (apiDeps.validate.isEmail(email)) {
                  return null;
                }
                return new Error('invalid email address');
              }
            }
          };
          var apiFactories = {
            memstoreFactory: { create: scopeMemstore }
          , systemSqlFactory: systemFactory
          };

          var hostsmap = {};

          function log(req, res, next) {
            var hostname = (req.hostname || req.headers.host || '').split(':').shift();

            // Printing all incoming requests for debugging
            console.log('[worker/log]', req.method, hostname, req.url);

            // logging all the invalid hostnames that come here out of curiousity
            if (hostname && !hostsmap[hostname]) {
              hostsmap[hostname] = true;
              require('fs').writeFile(
                require('path').join(__dirname, '..', '..', 'var', 'hostnames', hostname)
              , hostname
              , function () {}
              );
            }

            next();
          }

          function setupMain() {
            if (xconfx.debug) { console.log('[main] setup'); }
            mainApp = express();
            require('./main').create(mainApp, xconfx, apiFactories, apiDeps, errorIfApi, errorIfAssets).then(function () {
              if (xconfx.debug) { console.log('[main] ready'); }
              // TODO process.send({});
            });
          }

          if (!bootstrapApp) {
            if (xconfx.debug) { console.log('[bootstrap] setup'); }
            if (xconfx.primaryDomain) {
              bootstrapApp = true;
              setupMain();
              return;
            }
            bootstrapApp = express();
            require('./bootstrap').create(bootstrapApp, xconfx, models).then(function () {
              if (xconfx.debug) { console.log('[bootstrap] ready'); }
              // TODO process.send({});
              setupMain();
            });
          }

          process.on('message', function (data) {
            if ('com.daplie.walnut.bootstrap' === data.type) {
              setupMain();
            }
          });

          function errorIfNotApi(req, res, next) {
            var hostname = req.hostname || req.headers.host;

            if (!/^api\.[a-z0-9\-]+/.test(hostname)) {
              res.send({ error:
               { message: "['" + hostname + req.url + "'] API access is restricted to proper 'api'-prefixed lowercase subdomains."
                   + " The HTTP 'Host' header must exist and must begin with 'api.' as in 'api.example.com'."
                   + " For development you may test with api.localhost.daplie.me (or any domain by modifying your /etc/hosts)"
               , code: 'E_NOT_API'
               , _hostname: hostname
               }
              });
              return;
            }

            next();
          }

          function errorIfNotAssets(req, res, next) {
            var hostname = req.hostname || req.headers.host;

            if (!/^assets\.[a-z0-9\-]+/.test(hostname)) {
              res.send({ error:
               { message: "['" + hostname + req.url + "'] protected asset access is restricted to proper 'asset'-prefixed lowercase subdomains."
                   + " The HTTP 'Host' header must exist and must begin with 'assets.' as in 'assets.example.com'."
                   + " For development you may test with assets.localhost.daplie.me (or any domain by modifying your /etc/hosts)"
               , code: 'E_NOT_API'
               , _hostname: hostname
               }
              });
              return;
            }

            next();
          }

          function errorIfApi(req, res, next) {
            if (!/^api\./.test(req.headers.host)) {
              next();
              return;
            }

            // has api. hostname prefix

            // doesn't have /api url prefix
            if (!/^\/api\//.test(req.url)) {
              console.log('[walnut/worker api] req.url', req.url);
              res.send({ error: { message: "missing /api/ url prefix" } });
              return;
            }

            res.send({ error: { code: 'E_NO_IMPL', message: "API not implemented" } });
          }

          function errorIfAssets(req, res, next) {
            if (!/^assets\./.test(req.headers.host)) {
              next();
              return;
            }

            // has api. hostname prefix

            // doesn't have /api url prefix
            if (!/^\/assets\//.test(req.url)) {
              console.log('[walnut/worker assets] req.url', req.url);
              res.send({ error: { message: "missing /assets/ url prefix" } });
              return;
            }

            res.send({ error: { code: 'E_NO_IMPL', message: "assets handler not implemented" } });
          }

          app.disable('x-powered-by');
          app.use('/', log);
          app.use('/api', require('body-parser').json({
            strict: true // only objects and arrays
          , inflate: true
            // limited to due performance issues with JSON.parse and JSON.stringify
            // http://josh.zeigler.us/technology/web-development/how-big-is-too-big-for-json/
          //, limit: 128 * 1024
          , limit: 1.5 * 1024 * 1024
          , reviver: undefined
          , type: 'json'
          , verify: undefined
          }));
          app.use('/api', recase);

          var cookieParser = require('cookie-parser'); // signing is done in JWT

          app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']);
          app.use('/api', errorIfNotApi);
          app.use('/assets', /*errorIfNotAssets,*/ cookieParser()); // serializer { path: '/assets', httpOnly: true, sameSite: true/*, domain: assets.example.com*/ }
          app.use('/', function (req, res) {
            if (!(req.encrypted || req.secure)) {
              // did not come from https
              if (/\.(appcache|manifest)\b/.test(req.url)) {
                require('./unbrick-appcache').unbrick(req, res);
                return;
              }
              console.log('[lib/worker] unencrypted:', req.headers);
              res.end("Connection is not encrypted. That's no bueno or, as we say in Hungarian, nem szabad!");
              return;
            }

            // TODO check https://letsencrypt.status.io to see if https certification is not available

            if (mainApp) {
              mainApp(req, res);
              return;
            }
            else {
              bootstrapApp(req, res);
              return;
            }
          });

          return app;
        });
      });
    });
  });
};
