const http = require('http');
const httpProxy = require('http-proxy');
const axios = require('axios');
const URL = require('url');
const Redis = require("ioredis");

// CONFIG
const redisSentinelHost = process.env.REDIS_SENTINEL_HOST;
const redisSentinelPort = process.env.REDIS_SENTINEL_PORT;
const redisGroupName = process.env.REDIS_GROUP_NAME;
const redisAuthorizedTTL = process.env.REDIS_AUTHORIZED_TTL; // In seconds
const redisUnauthorizedTTL = process.env.REDIS_UNAUTHORIZED_TTL; // In seconds
const elasticsearchUrl = process.env.ELASTICSEARCH_URL;
const technicalAccountUsername = process.env.TECHNICAL_ACCOUNT_USERNAME;
const technicalAccountPassword = process.env.TECHNICAL_ACCOUNT_PASSWORD;
const proxyHostTarget = process.env.PROXY_HOST_TARGET;

// Configuring the different proxy server
// Proxy WMS
var wmsProxy = httpProxy.createProxyServer({
  changeOrigin: true,
  target: proxyHostTarget,
  auth: `${technicalAccountUsername}:${technicalAccountPassword}`,
});

// Configure and create an HTTP proxy server
var mvtProxy = httpProxy.createProxyServer({
  changeOrigin: true,
  target: proxyHostTarget,
  auth: `${technicalAccountUsername}:${technicalAccountPassword}`,
});

var mvtUnauthProxy = httpProxy.createProxyServer({
  changeOrigin: true,
  target: proxyHostTarget,
});

// keep a referece of the proxyRequest in the req object
wmsProxy.on('proxyReq', function (proxyReq, req) {
  req._proxyReq = proxyReq;
});

mvtProxy.on('proxyReq', function (proxyReq, req) {
  req._proxyReq = proxyReq;
});

mvtUnauthProxy.on('proxyReq', function (proxyReq, req) {
  req._proxyReq = proxyReq;
});

wmsProxy.on('error', function (err, req, res) {
  // If the client cancelled the request, abort the request to the upstream server
  if (req.socket.destroyed && err.code === 'ECONNRESET') {
    req._proxyReq.abort();
  }
  return console.log(`WMS Error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
});

mvtProxy.on('error', function (err, req, res) {
  // If the client cancelled the request, abort the request to the upstream server
  if (req.socket.destroyed && err.code === 'ECONNRESET') {
    req._proxyReq.abort();
  }
  return console.log(`MVT Error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
});

mvtUnauthProxy.on('error', function (err, req, res) {
  // If the client cancelled the request, abort the request to the upstream server
  if (req.socket.destroyed && err.code === 'ECONNRESET') {
    req._proxyReq.abort();
  }
  return console.log(`MVT Unauthenticated Error, req.socket.destroyed: ${req.socket.destroyed}, ${err}`);
});

// Create an HTTP server
http.createServer(async function (req, res) {

  /******************* WMS *****************************/
  if (req.url.includes('/wms')) {
    printLog('Request received', 'WMS');
    wmsProxy.web(req, res, {});
    printLog('WMS request', 'proxified');
    return;
  }

  /******************* MVT *****************************/
  if (req.url.includes('/mvt')) {
    printLog('Request received', 'MVT');

    // If no cookies then then we can't identify a user and directly proxy the request without credentials
    if (req.headers["x-anonymous-consumer"]) {
      printLog('Request received', `Unauthenticated - proxying the request`);
      mvtUnauthProxy.web(req, res, {});
      return;
    }

    // Read the requested layer from the url
    const layer = getParameterValueFromUrl(req.url, 'LAYERS');

    if (!layer) {
      printError('Request received', 'No layer provided');
      res.statusCode = 400;
      res.end();
      return;
    }

    printLog('Request received', `Layer found: ${layer}`);

    const userRightsOnTheLayer = await getRedisValue(`${layer}-${req.headers['x-consumer-username']}`);

    if (userRightsOnTheLayer === 'true') {
      printLog('Request received', 'Authorized (value read from Redis)');
      mvtProxy.web(req, res, {});
      return;
    }

    if (userRightsOnTheLayer === 'false') {
      printLog('Request received', 'Unauthorized (value read from Redis)');
      res.statusCode = 401;
      res.end();
      return;
    }

    const options = {
      method: 'POST',
      url: `${elasticsearchUrl}/_search?&request_cache=false`,
      data: {
        "from": 0,
        "size": 1,
        "_source": [
          "editorial-metadata.isSample",
          "editorial-metadata.isOpenAccess"
        ],
        "query": {
          "term": {
            "metadata-fr.link.name": layer
          }
        }
      },
      headers: {
        cookie: req.headers.cookie,
      }
    };

    printLog('Request received', options);

    let response;

    try {
      response = await axios(options)
    } catch (err) {
      printError('Request to ES failed', err);
      res.statusCode = 500;
      res.end();
      return;
    }


    // If no results are found it means the specified dataset mvt layer doesn't exists
    if (response.data.hits.hits < 1) {
      printError('Request to ES', 'MVT not found');
      res.statusCode = 404;
      res.end();
      return;
    }

    printLog('Request to ES', 'MVT found');

    const editorialMetadata = response.data.hits.hits[0]._source['editorial-metadata'];

    printLog('Request to ES', editorialMetadata);

    if (!editorialMetadata.isOpenAccess && editorialMetadata.isSample) {
      printError('Proxy', 'Unauthorized');
      setRedisValue(`${layer}-${req.headers['x-consumer-username']}`, false, redisUnauthorizedTTL);
      res.statusCode = 401;
      res.end();
      return;
    }

    await setRedisValue(`${layer}-${req.headers['x-consumer-username']}`, true, redisAuthorizedTTL);

    printLog('Proxy', 'Authorized');

    mvtProxy.web(req, res, {});
  }

}).listen(9000);

// HELPERS

function getParameterValueFromUrl(url, paramName) {

  const queryParams = URL.parse(url, true).query;

  return queryParams && queryParams[paramName] ? queryParams[paramName] : null;

}

async function setRedisValue(key, value, ttl) {
  const redisClient = new Redis({
    sentinels: [{
      host: redisSentinelHost,
      port: redisSentinelPort
    }, ],
    name: redisGroupName,
  });

  redisClient.on('error', (error) => {
    printError('Redis client', error);
    redisClient.disconnect();
  });


  // Set key value with expiration time in seconds
  const res = await redisClient.set(key, value, 'EX', ttl).catch((error) => {
    redisClient.disconnect();
    printError('Redis client', 'Couldn\'t set redis key/value (with ttl).');
    printError('Redis client', error);
    return false;
  });


  printLog('Redis client', 'Done setting key/value pair');
  redisClient.disconnect();

  return res ? true : false;
}

async function getRedisValue(key) {
  const redisClient = new Redis({
    sentinels: [{
      host: redisSentinelHost,
      port: redisSentinelPort
    }, ],
    name: redisGroupName,
  });

  redisClient.on('error', (error) => {
    printError('Redis client', error);
    redisClient.disconnect();
  });

  const res = await redisClient.get(key).catch((error) => {
    redisClient.disconnect();
    printError('Redis client', 'Couldn\'t get redis value.');
    printError('Redis client', error);
    return false;
  });

  printLog(`Redis client`, `Value found ${res}`);
  redisClient.disconnect();
  return res ? res : null;
}

function printLog(context, value) {
  console.log(`${new Date().toLocaleString("fr-FR")} [${context}] [log] `, value);
}

function printError(context, error) {
  console.error(`${new Date().toLocaleString("fr-FR")} [${context}] [error] `, error);
}