'use strict';

module.exports.create = function (xconfx, apiFactories, apiDeps) {
  var PromiseA = apiDeps.Promise;
  var express = require('express');
  var fs = PromiseA.promisifyAll(require('fs'));
  var path = require('path');
  var localCache = { rests: {}, pkgs: {} };

  // TODO xconfx.apispath
  xconfx.restPath = path.join(__dirname, '..', '..', 'packages', 'rest');
  xconfx.appApiGrantsPath = path.join(__dirname, '..', '..', 'packages', 'client-api-grants');

  function notConfigured(req, res) {
    var msg = "api package '" + req.pkgId + "' not configured for client uri '" + req.experienceId + "'"
      + ". To configure it place a new line '" + req.pkgId + "' in the file '/srv/walnut/packages/client-api-grants/" + req.experienceId + "'"
      ;

    res.send({ error: { message: msg } });
  }

  /*
  function isThisPkgInstalled(myConf, pkgId) {
  }
  */

  function isThisClientAllowedToUseThisPkg(myConf, clientUrih, pkgId) {
    var appApiGrantsPath = path.join(myConf.appApiGrantsPath, clientUrih);

    return fs.readFileAsync(appApiGrantsPath, 'utf8').then(function (text) {
      console.log('sanity', text);
      return text.trim().split(/\n/);
    }, function (err) {
      if ('ENOENT' !== err.code) {
        console.error(err);
      }
      return [];
    }).then(function (apis) {
      if (apis.some(function (api) {
        if (api === pkgId) {
          console.log(api, pkgId, api === pkgId);
          return true;
        }
      })) {
        return true;
      }
        if (clientUrih === ('api.' + xconfx.setupDomain) && 'org.oauth3.consumer' === pkgId) {
          // fallthrough
          return true;
        } else {
          return null;
        }
    });
  }

  function loadRestHelper(myConf, pkgId) {
    var pkgPath = path.join(myConf.restPath, pkgId);

    // TODO should not require package.json. Should work with files alone.
    return fs.readFileAsync(path.join(pkgPath, 'package.json'), 'utf8').then(function (text) {
      var pkg = JSON.parse(text);
      var deps = {};
      var myApp;

      if (pkg.walnut) {
        pkgPath = path.join(pkgPath, pkg.walnut);
      }

      Object.keys(apiDeps).forEach(function (key) {
        deps[key] = apiDeps[key];
      });
      Object.keys(apiFactories).forEach(function (key) {
        deps[key] = apiFactories[key];
      });

      // TODO pull db stuff from package.json somehow and pass allowed data models as deps
      //
      // how can we tell which of these would be correct?
      // deps.memstore = apiFactories.memstoreFactory.create(pkgId);
      // deps.memstore = apiFactories.memstoreFactory.create(req.experienceId);
      // deps.memstore = apiFactories.memstoreFactory.create(req.experienceId + pkgId);

      // let's go with this one for now and the api can choose to scope or not to scope
      deps.memstore = apiFactories.memstoreFactory.create(pkgId);

      console.log('DEBUG pkgPath', pkgPath);
      myApp = express();
      myApp.use('/', function preHandler(req, res, next) {
        req.originalUrl = req.originalUrl || req.url;
        // "/path/api/com.example/hello".replace(/.*\/api\//, '').replace(/([^\/]*\/+)/, '/') => '/hello'
        req.url = req.url.replace(/\/api\//, '').replace(/.*\/api\//, '').replace(/([^\/]*\/+)/, '/');
        console.log('[prehandler] req.url', req.url);
        next();
      });
      //
      // TODO handle /accounts/:accountId
      //
      return PromiseA.resolve(require(pkgPath).create({
        etcpath: xconfx.etcpath
      }/*pkgConf*/, deps/*pkgDeps*/, myApp/*myApp*/)).then(function (handler) {
        localCache.pkgs[pkgId] = { pkg: pkg, handler: handler || myApp, createdAt: Date.now() };
        return localCache.pkgs[pkgId];
      });
    });
  }

  // Read packages/apis/sub.sld.tld (forward dns) to find list of apis as tld.sld.sub (reverse dns)
  // TODO packages/allowed_apis/sub.sld.tld (?)
  // TODO auto-register org.oauth3.consumer for primaryDomain (and all sites?)
  function loadRestHandler(myConf, pkgId) {
    return PromiseA.resolve().then(function () {
      if (!localCache.pkgs[pkgId]) {
        return loadRestHelper(myConf, pkgId);
      }

      return localCache.pkgs[pkgId];
      // TODO expire require cache
      /*
      if (Date.now() - localCache.pkgs[pkgId].createdAt < (5 * 60 * 1000)) {
        return;
      }
      */
    }, function (/*err*/) {
      // TODO what kind of errors might we want to handle?
      return null;
    }).then(function (restPkg) {
      return restPkg;
    });
  }

  var CORS = require('connect-cors');
  var cors = CORS({ credentials: true, headers: [
    'X-Requested-With'
  , 'X-HTTP-Method-Override'
  , 'Content-Type'
  , 'Accept'
  , 'Authorization'
  ], methods: [ "GET", "POST", "PATCH", "PUT", "DELETE" ] });
  var staleAfter = (5 * 60 * 1000);

  return function (req, res, next) {
    cors(req, res, function () {
      console.log('[sanity check]', req.url);
      // Canonical client names
      // example.com should use api.example.com/api for all requests
      // sub.example.com/api should resolve to sub.example.com
      // example.com/subpath/api should resolve to example.com#subapp
      // sub.example.com/subpath/api should resolve to sub.example.com#subapp
      var clientUrih = req.hostname.replace(/^api\./, '') + req.url.replace(/\/api\/.*/, '/').replace(/\/+/g, '#').replace(/#$/, '');
      // Canonical package names
      // '/api/com.daplie.hello/hello' should resolve to 'com.daplie.hello'
      // '/subapp/api/com.daplie.hello/hello' should also 'com.daplie.hello'
      // '/subapp/api/com.daplie.hello/' may exist... must be a small api
      var pkgId = req.url.replace(/.*\/api\//, '').replace(/^\//, '').replace(/\/.*/, '');
      var now = Date.now();
      var hasBeenHandled = false;

      // Existing (Deprecated)
      Object.defineProperty(req, 'experienceId', {
        enumerable: true
      , configurable: false
      , writable: false
      , value: clientUrih
      });
      Object.defineProperty(req, 'apiId', {
        enumerable: true
      , configurable: false
      , writable: false
      , value: pkgId
      });

      // New
      Object.defineProperty(req, 'clientUrih', {
        enumerable: true
      , configurable: false
      , writable: false
        // TODO this identifier may need to be non-deterministic as to transfer if a domain name changes but is still the "same" app
        // (i.e. a company name change. maybe auto vs manual register - just like oauth3?)
        // NOTE: probably best to alias the name logically
      , value: clientUrih
      });
      Object.defineProperty(req, 'pkgId', {
        enumerable: true
      , configurable: false
      , writable: false
      , value: pkgId
      });

      // TODO cache permission (although the FS is already cached, NBD)
      return isThisClientAllowedToUseThisPkg(xconfx, clientUrih, pkgId).then(function (yes) {
        if (!yes) {
          notConfigured(req, res);
          return null;
        }

        if (localCache.rests[pkgId]) {
          localCache.rests[pkgId].handler(req, res, next);
          hasBeenHandled = true;

          if (now - localCache.rests[pkgId].createdAt > staleAfter) {
            localCache.rests[pkgId] = null;
          }
        }

        if (!localCache.rests[pkgId]) {
          //return doesThisPkgExist

          return loadRestHandler(xconfx, pkgId).then(function (myHandler) {
            if (!myHandler) {
              notConfigured(req, res);
              return;
            }

            localCache.rests[pkgId] = { handler: myHandler.handler, createdAt: now };
            if (!hasBeenHandled) {
              myHandler.handler(req, res, next);
            }
          });
        }
      });
    });
  };
};
