/* This file is part of Clementine.
   Copyright 2012-2014, John Maguire <john.maguire@gmail.com>
   Copyright 2014, Krzysztof Sobiecki <sobkas@gmail.com>
   Copyright 2014, David Sansome <me@davidsansome.com>

   Clementine is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Clementine is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Clementine.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "skydriveservice.h"

#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QUrlQuery>
#include <memory>

#include "core/application.h"
#include "core/player.h"
#include "core/waitforsignal.h"
#include "internet/core/oauthenticator.h"
#include "internet/skydrive/skydriveurlhandler.h"
#include "library/librarybackend.h"
#include "ui/iconloader.h"

namespace {

// Keep legacy name since it needs to match the database table name.
static const char* kServiceId = "skydrive";

static const char* kClientId = "905def38-34d2-4e32-8ba7-c37bcc329047";
static const char* kClientSecret = "";

// https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-auth-code-flow
static const char* kOAuthEndpoint =
    "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
static const char* kOAuthTokenEndpoint =
    "https://login.microsoftonline.com/common/oauth2/v2.0/token";
static const char* kOAuthScope =
    "User.Read Files.Read Files.Read.All offline_access";

// MS Graph API
// https://docs.microsoft.com/en-us/graph/api/overview?view=graph-rest-1.0
static const char* kGraphUserInfo = "https://graph.microsoft.com/v1.0/me";
static const char* kDriveBase = "https://graph.microsoft.com/v1.0/me/drive/";

}  // namespace

const char* SkydriveService::kServiceName = "OneDrive";
const char* SkydriveService::kSettingsGroup = "OneDrive";

SkydriveService::SkydriveService(Application* app, InternetModel* parent)
    : CloudFileService(app, parent, kServiceName, kServiceId,
                       IconLoader::Load("skydrive", IconLoader::Provider),
                       SettingsDialog::Page_Skydrive) {
  app->player()->RegisterUrlHandler(new SkydriveUrlHandler(this, this));
}

bool SkydriveService::has_credentials() const {
  return !refresh_token().isEmpty();
}

QString SkydriveService::refresh_token() const {
  QSettings s;
  s.beginGroup(kSettingsGroup);

  return s.value("refresh_token").toString();
}

void SkydriveService::Connect() {
  OAuthenticator* oauth = new OAuthenticator(
      kClientId, kClientSecret, OAuthenticator::RedirectStyle::LOCALHOST, this);
  if (!refresh_token().isEmpty()) {
    oauth->RefreshAuthorisation(kOAuthTokenEndpoint, refresh_token());
  } else {
    oauth->StartAuthorisation(kOAuthEndpoint, kOAuthTokenEndpoint, kOAuthScope);
  }

  NewClosure(oauth, SIGNAL(Finished()), this,
             SLOT(ConnectFinished(OAuthenticator*)), oauth);
}

void SkydriveService::ConnectFinished(OAuthenticator* oauth) {
  oauth->deleteLater();

  QSettings s;
  s.beginGroup(kSettingsGroup);
  s.setValue("refresh_token", oauth->refresh_token());

  access_token_ = oauth->access_token();
  expiry_time_ = oauth->expiry_time();

  FetchUserInfo();
}

void SkydriveService::FetchUserInfo() {
  QUrl url(kGraphUserInfo);
  QNetworkRequest request(url);
  AddAuthorizationHeader(&request);

  QNetworkReply* reply = network_->get(request);
  NewClosure(reply, SIGNAL(finished()), this,
             SLOT(FetchUserInfoFinished(QNetworkReply*)), reply);
}

QByteArray SkydriveService::GetAuthHeader() const {
  return QString("Bearer %1").arg(access_token_).toUtf8();
}

void SkydriveService::AddAuthorizationHeader(QNetworkRequest* request) {
  request->setRawHeader("Authorization", GetAuthHeader());
}

void SkydriveService::FetchUserInfoFinished(QNetworkReply* reply) {
  reply->deleteLater();

  QJsonDocument document = ParseJsonReply(reply);
  if (document.isNull()) return;

  QJsonObject json_response = document.object();

  QString name = json_response["displayName"].toString();
  if (!name.isEmpty()) {
    QSettings s;
    s.beginGroup(kSettingsGroup);
    s.setValue("name", name);
  }

  emit Connected();

  ListFiles();
}

void SkydriveService::ListFiles() { ListFiles("root"); }

void SkydriveService::ListFiles(const QString& folder) {
  QUrl url(QString(kDriveBase) + folder + "/children");
  QNetworkRequest request(url);
  AddAuthorizationHeader(&request);

  QNetworkReply* reply = network_->get(request);
  NewClosure(reply, SIGNAL(finished()), this,
             SLOT(ListFilesFinished(QNetworkReply*)), reply);
}

void SkydriveService::ListFilesFinished(QNetworkReply* reply) {
  reply->deleteLater();

  QJsonDocument document = ParseJsonReply(reply);
  if (document.isNull()) return;

  QJsonObject json_response = document.object();

  QJsonArray items = json_response["value"].toArray();
  for (const QJsonValue& f : items) {
    QJsonObject item = f.toObject();

    const QString id = item["id"].toString();
    const QString name = item["name"].toString();

    if (item.contains("folder")) {
      ListFiles(QString("items/%1").arg(id));
    } else if (item.contains("file")) {
      // The response provides a mime type, but it doesn't know about some
      // types that we care about.
      QString mime_type = GuessMimeTypeForFile(name);
      QUrl url;
      url.setScheme(GetScheme());
      url.setPath("/" + id);

      Song song;
      song.set_url(url);
      song.set_ctime(
          QDateTime::fromString(item["createdDateTime"].toString(), Qt::ISODate)
              .toTime_t());
      song.set_mtime(QDateTime::fromString(
                         item["lastModifiedDateTime"].toString(), Qt::ISODate)
                         .toTime_t());
      song.set_comment(item["description"].toString());
      song.set_filesize(item["size"].toInt());
      song.set_title(name);

      QUrl download_url = ItemUrl(id, "content");
      MaybeAddFileToDatabase(song, mime_type, download_url,
                             QString("Bearer %1").arg(access_token_));
    } else {
      qLog(Debug) << "Unknown item type for" << name;
    }
  }
}

QUrl SkydriveService::ItemUrl(const QString& id, const QString& path) {
  return QUrl(QString(kDriveBase) + "items/" + id + "/" + path);
}

QUrl SkydriveService::GetStreamingUrlFromSongId(const QString& file_id) {
  EnsureConnected();

  return ItemUrl(file_id, "content");
}

void SkydriveService::PopulateContextMenu() {
  context_menu_->addActions(GetPlaylistActions());
  full_rescan_action_ = context_menu_->addAction(
      IconLoader::Load("view-refresh", IconLoader::Base),
      tr("Do a full rescan..."), this, SLOT(FullRescanRequested()));
  context_menu_->addSeparator();
  context_menu_->addAction(IconLoader::Load("download", IconLoader::Base),
                           tr("Cover Manager"), this, SLOT(ShowCoverManager()));
  context_menu_->addSeparator();
  context_menu_->addAction(IconLoader::Load("configure", IconLoader::Base),
                           tr("Configure..."), this,
                           SLOT(ShowSettingsDialog()));
}

void SkydriveService::UpdateContextMenu() {
  full_rescan_action_->setEnabled(!is_indexing());
}

void SkydriveService::EnsureConnected() {
  if (!access_token_.isEmpty()) {
    return;
  }

  Connect();
  WaitForSignal(this, SIGNAL(Connected()));
}

void SkydriveService::ForgetCredentials() {
  QSettings s;
  s.beginGroup(kSettingsGroup);

  s.remove("refresh_token");
  s.remove("name");
}

void SkydriveService::DoFullRescan() {
  library_backend_->DeleteAll();
  ListFiles();
}
