
#include "HTTPService.h"
#include "Platform.h"
#include "Version.h"

#include "util/debug.h"

static const char UserAgent[] PROGMEM = "influxdb-client-arduino/" INFLUXDB_CLIENT_VERSION " (" INFLUXDB_CLIENT_PLATFORM " " INFLUXDB_CLIENT_PLATFORM_VERSION ")";

#if defined(ESP8266)         
bool checkMFLN(BearSSL::WiFiClientSecure  *client, String url);
#endif

// This cannot be put to PROGMEM due to the way how it is used
static const char *RetryAfter = "Retry-After";
const char *TransferEncoding = "Transfer-Encoding";

HTTPService::HTTPService(ConnectionInfo *pConnInfo):_pConnInfo(pConnInfo) {
  _apiURL = pConnInfo->serverUrl;
  _apiURL += "/api/v2/";
  bool https = pConnInfo->serverUrl.startsWith("https");
  if(https) {
#if defined(ESP8266)         
    BearSSL::WiFiClientSecure *wifiClientSec = new BearSSL::WiFiClientSecure;
    if (pConnInfo->insecure) {
      wifiClientSec->setInsecure();
    } else if(pConnInfo->certInfo && strlen_P(pConnInfo->certInfo) > 0) {
      if(strlen_P(pConnInfo->certInfo) > 60 ) { //differentiate fingerprint and cert
         _cert = new BearSSL::X509List(pConnInfo->certInfo); 
         wifiClientSec->setTrustAnchors(_cert);
      } else {
         wifiClientSec->setFingerprint(pConnInfo->certInfo);
      }
    }
    checkMFLN(wifiClientSec, pConnInfo->serverUrl);
#elif defined(ESP32)
    WiFiClientSecure *wifiClientSec = new WiFiClientSecure;  
    if (pConnInfo->insecure) {
#ifndef ARDUINO_ESP32_RELEASE_1_0_4
      // This works only in ESP32 SDK 1.0.5 and higher
      wifiClientSec->setInsecure();
#endif            
    } else if(pConnInfo->certInfo && strlen_P(pConnInfo->certInfo) > 0) { 
      wifiClientSec->setCACert(pConnInfo->certInfo);
    }
#endif    
    _wifiClient = wifiClientSec;
  } else {
    _wifiClient = new WiFiClient;
  }
  if(!_httpClient) {
    _httpClient = new HTTPClient;
  }
  _httpClient->setReuse(_pConnInfo->httpOptions._connectionReuse);

  _httpClient->setUserAgent(FPSTR(UserAgent));
};

HTTPService::~HTTPService() {
  if(_httpClient) {
    delete _httpClient;
    _httpClient = nullptr;
  }
  if(_wifiClient) {
    delete _wifiClient;
    _wifiClient = nullptr;
  }
#if defined(ESP8266)     
  if(_cert) {
    delete _cert;
    _cert = nullptr;
}
#endif
}


void HTTPService::setHTTPOptions() {
  if(!_httpClient) {
    _httpClient = new HTTPClient;
  }
  _httpClient->setReuse(_pConnInfo->httpOptions._connectionReuse);
  _httpClient->setTimeout(_pConnInfo->httpOptions._httpReadTimeout);
#if defined(ESP32) 
  _httpClient->setConnectTimeout(_pConnInfo->httpOptions._httpReadTimeout);
#endif
}

// parse URL for host and port and call probeMaxFragmentLength
#if defined(ESP8266)         
bool checkMFLN(BearSSL::WiFiClientSecure  *client, String url) {
    int index = url.indexOf(':');
     if(index < 0) {
        return false;
    }
    String protocol = url.substring(0, index);
    int port = -1;
    url.remove(0, (index + 3)); // remove http:// or https://

    if (protocol == "http") {
        // set default port for 'http'
        port = 80;
    } else if (protocol == "https") {
        // set default port for 'https'
        port = 443;
    } else {
        return false;
    }
    index = url.indexOf('/');
    String host = url.substring(0, index);
    url.remove(0, index); // remove host 
    // check Authorization
    index = host.indexOf('@');
    if(index >= 0) {
        host.remove(0, index + 1); // remove auth part including @
    }
    // get port
    index = host.indexOf(':');
    if(index >= 0) {
        String portS = host;
        host = host.substring(0, index); // hostname
        portS.remove(0, (index + 1)); // remove hostname + :
        port = portS.toInt(); // get port
    }
    INFLUXDB_CLIENT_DEBUG("[D] probeMaxFragmentLength to %s:%d\n", host.c_str(), port);
    bool mfln = client->probeMaxFragmentLength(host, port, 1024);
    INFLUXDB_CLIENT_DEBUG("[D]  MFLN:%s\n", mfln ? "yes" : "no");
    if (mfln) {
        client->setBufferSizes(1024, 1024);
    } 
    return mfln;
}
#endif //ESP8266

bool HTTPService::beforeRequest(const char *url) {
   if(!_httpClient->begin(*_wifiClient, url)) {
    _pConnInfo->lastError = F("begin failed");
    return false;
  }
  if(_pConnInfo->authToken.length() > 0) {
    _httpClient->addHeader(F("Authorization"), "Token " + _pConnInfo->authToken);
  }
  const char * headerKeys[] = {RetryAfter, TransferEncoding} ;
  _httpClient->collectHeaders(headerKeys, 2);
  return true;
}

bool HTTPService::doPOST(const char *url, const char *data, const char *contentType, int expectedCode, httpResponseCallback cb) {
  INFLUXDB_CLIENT_DEBUG("[D] POST request - %s, data: %dbytes, type %s\n", url, strlen(data), contentType);
  if(!beforeRequest(url)) {
    return false;
  }
  if(contentType) {
    _httpClient->addHeader(F("Content-Type"), FPSTR(contentType));
  }
  _lastStatusCode = _httpClient->POST((uint8_t *) data, strlen(data));
  return afterRequest(expectedCode, cb);
}

bool HTTPService::doPOST(const char *url, Stream *stream, const char *contentType, int expectedCode, httpResponseCallback cb) {
  INFLUXDB_CLIENT_DEBUG("[D] POST request - %s, data: %dbytes, type %s\n", url, stream->available(), contentType);
  if(!beforeRequest(url)) {
    return false;
  }
  if(contentType) {
    _httpClient->addHeader(F("Content-Type"), FPSTR(contentType));
  }
  _lastStatusCode = _httpClient->sendRequest("POST", stream, stream->available());
  return afterRequest(expectedCode, cb);
}

bool HTTPService::doGET(const char *url, int expectedCode, httpResponseCallback cb) {
  INFLUXDB_CLIENT_DEBUG("[D] GET request - %s\n", url);
  if(!beforeRequest(url)) {
    return false;
  }
  _lastStatusCode = _httpClient->GET();
  return afterRequest(expectedCode, cb, false);
}

bool HTTPService::doDELETE(const char *url, int expectedCode, httpResponseCallback cb) {
  INFLUXDB_CLIENT_DEBUG("[D] DELETE - %s\n", url);
  if(!beforeRequest(url)) {
    return false;
  }
  _lastStatusCode = _httpClient->sendRequest("DELETE");
  return afterRequest(expectedCode, cb, false);
}

bool HTTPService::afterRequest(int expectedStatusCode, httpResponseCallback cb,  bool modifyLastConnStatus) {
    if(modifyLastConnStatus) {
        _lastRequestTime = millis();
        INFLUXDB_CLIENT_DEBUG("[D] HTTP status code - %d\n", _lastStatusCode);
        _lastRetryAfter = 0;
        if(_lastStatusCode >= 429) { //retryable server errors
            if(_httpClient->hasHeader(RetryAfter)) {
                _lastRetryAfter = _httpClient->header(RetryAfter).toInt();
                INFLUXDB_CLIENT_DEBUG("[D] Reply after - %d\n", _lastRetryAfter);
            }
        }
    }
    _pConnInfo->lastError = (char *)nullptr;
    bool ret = _lastStatusCode == expectedStatusCode;
    bool endConnection = true;
    if(!ret) {
        if(_lastStatusCode > 0) {
            _pConnInfo->lastError = _httpClient->getString();
            INFLUXDB_CLIENT_DEBUG("[D] Response:\n%s\n", _pConnInfo->lastError.c_str());
        } else {
            _pConnInfo->lastError = _httpClient->errorToString(_lastStatusCode);
            INFLUXDB_CLIENT_DEBUG("[E] Error - %s\n", _pConnInfo->lastError.c_str());
        }
    } else if(cb){
      endConnection = cb(_httpClient);
    }
    if(endConnection) {
        _httpClient->end();
    }
    return ret;
}