//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
// .NAME qtNewtInterface.h

#ifndef smtk_newt_qtNewtInterface_h
#define smtk_newt_qtNewtInterface_h

#include "smtk/newt/Exports.h"

#include <QByteArray>
#include <QJsonObject>
#include <QList>
#include <QObject>
#include <QSslError>
#include <QString>

#include <nlohmann/json.hpp>

#include <string>

class QFile;
class QNetworkAccessManager;
class QNetworkReply;

namespace newt
{

/**\brief Singleton class for connecting to NERSC using NEWT api.
 */
class SMTKNEWT_EXPORT qtNewtInterface : public QObject
{
  Q_OBJECT

public:
  static qtNewtInterface* instance(QObject* parent = nullptr);
  ~qtNewtInterface() override;

  bool isLoggedIn() const { return m_isLoggedIn; }

  // QString getScratchDir() const;

  // Login to NERSC
  // password argument must be <user-password + MFA-onetime-password>
  void login(const QString& username, const QString& password);

  // Backdoor login **for testing only**
  void mockLogin(const QString& username, const QString& newtSessionId);

  // Parses reply to return output object and error message.
  // Returns boolean indicating true if reply is OK
  bool parseReply(QNetworkReply* reply, QJsonObject& j, QString& error) const;
  bool parseReply(QNetworkReply* reply, nlohmann::json& j, std::string& error) const;

  // Parses command reply to return output and/or error message
  void getCommandReply(QNetworkReply* reply, QString& result, QString& error) const;

  QNetworkAccessManager* networkAccessManager() const { return m_networkManager; }

  QString newtSessionId() const { return m_newtSessionId; }

  QNetworkReply* requestDirectoryList(const QString& machine, const QString& path);

  // Start file download request.
  QNetworkReply* requestFileDownload(const QString& machine, const QString& remotePath);

  // Start file upload. Check reply for status code 200
  // localFilePath is the file to upload
  // remotePath is the path to the directory on the remote machine
  // Returns nullptr if the local file is not found or cannot be read
  QNetworkReply* requestFileUpload(
    const QString& localFilePath,
    const QString& remotePath,
    const QString& machine);

  // Start request to get user's $HOME path
  QNetworkReply* requestHomePath(const QString& machine);

  // Start request to submit job to queue.
  // remoteFilePath is the path to the slurm script on the target machine.
  QNetworkReply* requestJobSubmit(const QString& remoteFilePath, const QString& machine);

  // Start requrest to get user's $SCRATCH path
  QNetworkReply* requestScratchPath(const QString& machine);

  // Start command request
  QNetworkReply* sendCommand(const QString& command, const QString& machine);

  QString userName() const { return m_username; }

signals:
  void error(const QString& message, QNetworkReply* networkReply);
  void loginComplete(const QString& userName, int lifetime);
  void loginFail(const QString message);

protected slots:
  void onLoginReply();
  void onSslErrors(const QList<QSslError>& errors);

protected:
  qtNewtInterface(QObject* parent = nullptr);

  QString getErrorMessage(QNetworkReply* reply, const QByteArray& bytes) const;
  void onReadyRead(QNetworkReply* reply, QFile* file) const;
  QNetworkReply* sendGetRequest(const QString& url);

  bool m_isLoggedIn;
  QNetworkAccessManager* m_networkManager;
  QString m_newtSessionId;
  QString m_username;

private:
  Q_DISABLE_COPY(qtNewtInterface);
};

} // namespace newt

#endif
