#include "networkmanager.h"
#include <QElapsedTimer>
#include <QJsonDocument>
#include <QNetworkReply>

namespace
{
constexpr char API_URL[] = "https://orion.dev.flowy.live";
}

NetworkManager::NetworkManager()
{
  m_qnam = new QNetworkAccessManager(this);
  connect(m_qnam, &QNetworkAccessManager::finished, this, [this](QNetworkReply *reply) {
    // In case particular instances of QNetworkReply are not cleaned up.
    // It's safe to be redundant with deleteLater
    // Calling deleteLater here also doesn't disrupt QNetworkReply::finished slots, as this both here
    //   and in those slots are fired in tandem and deleteLater waits for return to event loop to perform deleteLater
    reply->deleteLater();
    int statusCode = reply->attribute(QNetworkRequest::Attribute::HttpStatusCodeAttribute).toInt();
    qDebug() << "Network request status code: " << statusCode;
    if (statusCode == 401)
    {
      qWarning() << "Unauthorized request: " << reply->errorString();
      emit unauthorizedDetected();
    }
  });
}

NetworkManager::~NetworkManager()
{
}

NetworkManager &NetworkManager::instance()
{
  static NetworkManager instance;
  return instance;
}

void NetworkManager::setAuthHeader(QNetworkRequest &request)
{
  if (m_sessionToken.isEmpty())
  {
    return;
  }
  QString headerValue = QString("Bearer %1").arg(m_sessionToken);
  request.setRawHeader("Authorization", headerValue.toUtf8());
}

QString NetworkManager::getFullUrl(const QString &url)
{
  return QString("%1%2").arg(API_URL, url);
}

QNetworkReply *NetworkManager::get(const QString &path)
{
  QNetworkRequest req = QNetworkRequest(getFullUrl(path));
  setAuthHeader(req);
  return m_qnam->get(req);
}

QNetworkReply *NetworkManager::post(const QString &path, const QJsonDocument &doc)
{
  QNetworkRequest request = QNetworkRequest(getFullUrl(path));
  setAuthHeader(request);
  request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
  return m_qnam->post(request, doc.toJson());
}

QNetworkReply *NetworkManager::put(const QString &path, const QJsonDocument &doc)
{
  QNetworkRequest request = QNetworkRequest(getFullUrl(path));
  setAuthHeader(request);
  request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
  return m_qnam->put(request, doc.toJson());
}

QNetworkReply *NetworkManager::patch(const QString &path, const QJsonDocument &doc)
{
  QNetworkRequest request = QNetworkRequest(getFullUrl(path));
  setAuthHeader(request);
  request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
  return m_qnam->sendCustomRequest(request, "PATCH", doc.toJson());
}

QNetworkReply *NetworkManager::del(const QString &path)
{
  QNetworkRequest request = QNetworkRequest(getFullUrl(path));
  setAuthHeader(request);
  return m_qnam->deleteResource(request);
}

QNetworkReply *NetworkManager::del(const QString &path, const QJsonDocument &doc)
{
  QNetworkRequest request = QNetworkRequest(getFullUrl(path));
  setAuthHeader(request);
  request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
  return m_qnam->sendCustomRequest(request, "DELETE", doc.toJson());
}

QNetworkRequest NetworkManager::buildRequest(const QString &path)
{
  QNetworkRequest request(getFullUrl(path));
  setAuthHeader(request);
  return request;
}

QString NetworkManager::sessionToken() const
{
  return m_sessionToken;
}

void NetworkManager::setSessionToken(const QString &newToken)
{
  m_sessionToken = newToken;
}

class PingTimer : public QObject
{
public:
  QElapsedTimer timer;
  explicit PingTimer(QObject *parent = nullptr) : QObject(parent) { timer.start(); }
};

void NetworkManager::ping()
{
  auto *pingTimer = new PingTimer(this);

  QNetworkRequest request(getFullUrl(""));
  QNetworkReply *reply = m_qnam->get(request);

  connect(reply, &QNetworkReply::finished, pingTimer, [this, pingTimer, reply]() {
    int elapsed = static_cast<int>(pingTimer->timer.elapsed());
    pingTimer->deleteLater();

    int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
    if (statusCode > 0)
    {
      emit pingResult(elapsed);
    }
    else
    {
      emit pingFailed();
    }
  });
}
