/*
 * Copyright 2014-2021 Firestack, all rights reserved.
 */
import * as url from 'url';
import * as fs from 'fs';
import axios from 'axios';
import { IHeaders, } from 'soap';
import * as request from 'request';
import * as httpNtlm from 'httpntlm';

export class CustomHttpClientOptions {
  sdkCache = true;
  sdkCacheProfile = 'default';
  constructor(init?: Partial<CustomHttpClientOptions>) {
    if (init) { Object.assign(this, init); }
    if (!this.sdkCacheProfile) { this.sdkCacheProfile = 'default'; }
  }
}

export const sdkCaches = {
  'core-types.xsd': 1,
  'query-messagetypes.xsd': 1,
  'query-types.xsd': 1,
  'reflect-messagetypes.xsd': 1,
  'reflect-types.xsd': 1,
  'vim-messagetypes.xsd': 1,
  'vim-types.xsd': 1,
  'vim.wsdl': 1,
};

export class CustomHttpClient {
  // tslint:disable-next-line: variable-name
  _request = request;
  config: CustomHttpClientOptions;
  constructor(options?: Partial<CustomHttpClientOptions>) {
    this.config = new CustomHttpClientOptions(options);
  }
  /**
   * Build the HTTP request (method, uri, headers, ...)
   * @param rurl The resource url
   * @param data The payload
   * @param exheaders Extra http headers
   * @param exoptions Extra options
   * @returns The http request object for the `request` module
   */
  buildRequest(rurl: string, data: any, exheaders?: IHeaders, exoptions?: any): any {
    if (exoptions === void 0) { exoptions = {}; }
    // tslint:disable-next-line: deprecation
    const curl = url.parse(rurl);
    // const secure = curl.protocol === 'https:';
    const host = curl.hostname;
    const port = parseInt(curl.port, 10);
    // const path = [curl.pathname || '/', curl.search || '', curl.hash || ''].join('');
    const method = data ? 'POST' : 'GET';
    const headers = {
        'User-Agent': 'node-soap-custom-http-client',
        Accept: 'text/html,application/xhtml+xml,application/xml,text/xml;q=0.9,*/*;q=0.8',
        'Accept-Encoding': 'none',
        'Accept-Charset': 'utf-8',
        Connection: exoptions.forever ? 'keep-alive' : 'close',
        Host: host + (isNaN(port) ? '' : ':' + port)
    };
    const mergeOptions = ['headers'];
    // tslint:disable-next-line: variable-name
    const {attachments: _attachments, ...newExoptions } = exoptions;
    const attachments = _attachments || [];
    if (typeof data === 'string' && attachments.length === 0 && !exoptions.forceMTOM) {
        headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
        headers['Content-Type'] = 'application/x-www-form-urlencoded';
    }
    exheaders = exheaders || {};
    for (const attr of Object.keys(exheaders)) { headers[attr] = exheaders[attr]; }
    const options: any = {
        uri: curl,
        method, headers,
        followAllRedirects: true
    };
    options.body = data;
    for (const attr of Object.keys(newExoptions)) {
        if (mergeOptions.indexOf(attr) !== -1) {
          for (const header of Object.keys(exoptions[attr])) {
              options[attr][header] = exoptions[attr][header];
          }
        } else {
            options[attr] = exoptions[attr];
        }
    }
    return options;
  }

  /**
   * Handle the http response
   * @param reqObj The req object
   * @param res The res object
   * @param body The http body
   * @param The parsed body
   */
  handleResponse(reqObj: any, res: any, body: any): any { return body; }
  // handleResponse(reqObj: req.Request, res: req.Response, body: any): any { return body; }

  /** Overriden to axios requests */
  request(rurl: string, data: any, callback: (error: any, res?: any, body?: any) => any,
          exheaders?: IHeaders, exoptions?: any, caller?: any): void {
    const options = this.buildRequest(rurl, data, exheaders, exoptions);
    let reqObj;
    if (exoptions !== undefined && exoptions.hasOwnProperty('ntlm')) {
        // sadly when using ntlm nothing to return
        // Not sure if this can be handled in a cleaner way rather than an if/else,
        // will to tidy up if I get chance later, patches welcome - insanityinside
        // TODO - should the following be uri?
        options.url = rurl;
        httpNtlm[options.method.toLowerCase()](options, (err, res) => {
            if (err) {
                return callback(err);
            }
            // if result is stream
            if (typeof res.body !== 'string') {
                res.body = res.body.toString();
            }
            res.body = this.handleResponse(reqObj, res, res.body);
            callback(null, res, res.body);
        });
    } else {
        const urlObj = options.url = options.uri;
        options.data = options.body;
        // Use cached SDK interface definitions if hit
        if (this.config.sdkCache && urlObj.path.startsWith('/sdk/')) {
          const target = urlObj.path.split('/sdk/')[1];
          if (sdkCaches[target]) {
            fs.readFile(`${__dirname}/sdk-cache/${this.config.sdkCacheProfile}/${target}`, (e, dataBuffer) => {
              // tslint:disable-next-line: no-console
              if (e) { console.log(e); return; }
              const dat = dataBuffer.toString('utf8');
              const code = 200;
              const res = { status: code, statusCode: code, body: dat, data: dat };
              callback(null, res, res.body);
            });
            return;
          }
        }
        // ! override to axios
        if (typeof options.url === 'object') {
          options.url = options.url.href;
        }
        reqObj = axios(options).then(res => {
          (res as any).body = res.data;
          (res as any).statusCode = res.status;
          // fs.writeFile(`isolate-${Date.now()}.xml`, options.url.path + '\n' + res.data, () => {});
          callback(null, res, res.data);
        }).catch(e => {
          callback(e);
        });
    }
  }

  // requestStream(rurl: string, data: any, exheaders?: IHeaders, exoptions?: any, caller?: any): req.Request {
  requestStream(rurl: string, data: any, exheaders?: IHeaders, exoptions?: any, caller?: any): any {
    const options = this.buildRequest(rurl, data, exheaders, exoptions);
    return this._request(options);
  }
}

