const {
  option: {Some, None},
  Instrumentation
} = require('zipkin');

const CLSContext = require('zipkin-context-cls');

import url = require('url');
import * as expressTypes from "express";
import * as zipkin from "zipkin";
import Settings from '../../settings';

function formatRequestUrl(req:expressTypes.Request) {
  const parsed = url.parse(req.originalUrl);
  return url.format({
    protocol: req.protocol,
    host: req.get('host'),
    pathname: parsed.pathname,
    search: parsed.search
  });
}

module.exports = function expressMiddleware({tracer, serviceName, port = 0} : {tracer:zipkin.Tracer, serviceName:string, port: number}) {
  const instrumentation = new Instrumentation.HttpServer({tracer, serviceName, port});
  return function zipkinExpressMiddleware(req:expressTypes.Request, res:expressTypes.Response, next:expressTypes.NextFunction) {
    
    let context = new CLSContext(Settings.ZIPKIN_CONTEXT_NAME);
    tracer.scoped(() => {
      (<any>tracer)['_ctxImpl'] = context;
      function readHeader(header:string) {
        const val = req.header(header);
        if (val != null) {
          return new Some(val);
        } else {
          return None;
        }
      }
      
      const id = instrumentation.recordRequest(req.method, formatRequestUrl(req), readHeader);
      
      Object.defineProperty(req, '_trace_id', {configurable: false, get: () => id});
      
      /**
      * add body 
      */
      if("POST" == req.method && req.body != undefined){
        tracer.recordBinary('POST_BODY', JSON.stringify(req.body));
      }
      
      /**
      * store the ids in the res.locals area
      */
      res.locals.spanId  = id.traceId;
      res.locals.traceId  = id.spanId;
      
      /*
      //DONT KNOW if we need these atm, if needed uncomment the block
      //store parent id
      id._parentId.ifPresent(function (psid:string) {
        res.locals.parentId = psid;
      });
      
      //store sampled flag
      id.sampled.ifPresent(function (sampled:string) {
        res.locals.sampled = sampled ? '1' : '0';
      });
      */
      
      /**
      * records response on finish or close (whichever happens first)
      * @listens close
      * @listens finish
      */
      const onCloseOrFinish = () => {
        res.removeListener('close', onCloseOrFinish);
        res.removeListener('finish', onCloseOrFinish);
        
        tracer.letId(id, () => {
          tracer.recordRpc(req.method);
          let code = res.statusCode;
          let message = res.statusMessage;
          instrumentation.recordResponse(id, code, code == 200 ? undefined : new Error(message || 'error'));
        });
      };
      
      res.once('close', onCloseOrFinish);
      res.once('finish', onCloseOrFinish);
      
      next();
    });
  };
};