/* This file is part of Clementine.
   Copyright 2014, Vlad Maltsev <shedwardx@gmail.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 <QDateTime>
#include <QDesktopServices>

#include <qjson/parser.h>

#include "core/closure.h"
#include "core/logging.h"
#include "internet/localredirectserver.h"
#include "vreen/utils.h"

#include "vkservice.h"

static const QUrl kVkOAuthEndpoint("https://oauth.vk.com/authorize");
static const QUrl kVkOAuthTokenEndpoint("https://oauth.vk.com/access_token");
static const QUrl kApiUrl("https://api.vk.com/method/");
static const char *kScopeNames[] = { "notify", "friends", "photos", "audio",
  "video", "docs", "notes", "pages", "status", "offers", "questions", "wall",
  "groups", "messages", "notifications", "stats", "ads", "offline" };

static const QString kAppID = "3421812";
static const QString kAppSecret = "cY7KMyX46Fq3nscZlbdo";
static const VkConnection::Scopes kScopes =
  VkConnection::Offline |
  VkConnection::Audio |
  VkConnection::Friends |
  VkConnection::Groups;

static const char* kSettingsGroup = "Vk.com/oauth";

VkConnection::VkConnection(QObject* parent)
  : Connection(parent),
    state_(Vreen::Client::StateOffline),
    expires_in_(0),
    uid_(0) {
  loadToken();
}

VkConnection::~VkConnection() {
}

void VkConnection::connectToHost(const QString& login, const QString& password) {
  Q_UNUSED(login)
  Q_UNUSED(password)
  if (hasAccount()) {
    setConnectionState(Vreen::Client::StateOnline);
  } else {
    setConnectionState(Vreen::Client::StateConnecting);
    requestAccessToken();
  }
}

void VkConnection::disconnectFromHost() {
  clear();
  setConnectionState(Vreen::Client::StateOffline);
}

void VkConnection::clear() {
  access_token_.clear();
  expires_in_ = 0;
  uid_ = 0;

  QSettings s;
  s.beginGroup(kSettingsGroup);
  s.remove("access_token");
  s.remove("expires_in");
  s.remove("uid");
}

bool VkConnection::hasAccount() {
  return !access_token_.isNull()
      && (expires_in_ > static_cast<time_t>(QDateTime::currentDateTime().toTime_t()));
}

QNetworkRequest VkConnection::makeRequest(const QString& method, const QVariantMap& args) {
  QUrl url = kApiUrl;
  url.setPath(url.path() % QLatin1Literal("/") % method);
  for (auto it = args.constBegin(); it != args.constEnd(); ++it) {
    url.addQueryItem(it.key(),
                     it.value().toString());
  }
  url.addEncodedQueryItem("access_token", access_token_);
  return QNetworkRequest(url);
}

void VkConnection::decorateRequest(QNetworkRequest& request) {
  QUrl url = request.url();
  url.addEncodedQueryItem("access_token", access_token_);
  request.setUrl(url);
}

void VkConnection::requestAccessToken() {
  LocalRedirectServer* server = new LocalRedirectServer(this);
  server->Listen();

  QUrl url = kVkOAuthEndpoint;
  url.addQueryItem("client_id", kAppID);
  url.addQueryItem("scope",
                   Vreen::flagsToStrList(kScopes, kScopeNames).join(","));
  url.addQueryItem("redirect_uri", server->url().toString());
  url.addQueryItem("response_type", "code");

  qLog(Debug) << "Try to login to Vk.com" << url;

  NewClosure(server, SIGNAL(Finished()),
             this, SLOT(codeRecived(LocalRedirectServer*, QUrl)),
             server, server->url());
  QDesktopServices::openUrl(url);
}

void VkConnection::codeRecived(LocalRedirectServer* server, QUrl redirect_uri) {
  if (server->request_url().hasQueryItem("code")) {
    code_ = server->request_url().queryItemValue("code").toUtf8();
    QUrl url = kVkOAuthTokenEndpoint;
    url.addQueryItem("client_id", kAppID);
    url.addQueryItem("client_secret", kAppSecret);
    url.addQueryItem("code", QString::fromUtf8(code_));
    url.addQueryItem("redirect_uri", redirect_uri.toString());
    qLog(Debug) << "Getting access token" << url;

    QNetworkRequest request(url);
    QNetworkReply* reply = network_.get(request);

    NewClosure(reply, SIGNAL(finished()), this,
               SLOT(accessTokenRecived(QNetworkReply*)), reply);
  } else {
    qLog(Error) << "Login failed" << server->request_url();
    clear();
    emit connectionStateChanged(Vreen::Client::StateOffline);
  }
}

void VkConnection::accessTokenRecived(QNetworkReply* reply) {
  if (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) != 200) {
    qLog(Error) << "Failed to get access token" << reply->readAll();
    emit setConnectionState(Vreen::Client::StateOffline);
    clear();
    return;
  }

  QJson::Parser parser;
  bool ok = false;
  QByteArray reply_data = reply->readAll();
  QVariantMap result = parser.parse(reply_data, &ok).toMap();
  if (!ok) {
    qLog(Error) << "Failed to parse oauth reply" << reply_data;
    emit setConnectionState(Vreen::Client::StateOffline);
    clear();
    return;
  }
  qLog(Debug) << result;

  access_token_ = result["access_token"].toByteArray();
  expires_in_ = result["expires_in"].toUInt();
  uid_ = result["user_id"].toInt();

  if (expires_in_) {
    expires_in_ += QDateTime::currentDateTime().toTime_t();
  } else {
    expires_in_ += QDateTime::currentDateTime().addMonths(1).toTime_t();
  }
  qLog(Debug) << "Access token expires in" << expires_in_;

  saveToken();
  setConnectionState(Vreen::Client::StateOnline);

  reply->deleteLater();
}

void VkConnection::setConnectionState(Vreen::Client::State state) {
  if (state != state_) {
    state_ = state;
    emit connectionStateChanged(state);
  }
}

void VkConnection::saveToken() {
  QSettings s;
  s.beginGroup(kSettingsGroup);
  s.setValue("access_token", access_token_);
  s.setValue("expires_in", QVariant::fromValue(expires_in_));
  s.setValue("uid", uid_);
}

void VkConnection::loadToken() {
  QSettings s;
  s.beginGroup(kSettingsGroup);
  access_token_ = s.value("access_token").toByteArray();
  expires_in_ = s.value("expires_in").toUInt();
  uid_ = s.value("uid").toInt();
}
