//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/GameManager.h"

#include "td/telegram/AccessRights.h"
#include "td/telegram/AuthManager.h"
#include "td/telegram/ChainId.h"
#include "td/telegram/ContactsManager.h"
#include "td/telegram/DialogId.h"
#include "td/telegram/Global.h"
#include "td/telegram/InlineQueriesManager.h"
#include "td/telegram/MessageId.h"
#include "td/telegram/MessagesManager.h"
#include "td/telegram/net/DcId.h"
#include "td/telegram/net/NetQueryCreator.h"
#include "td/telegram/Td.h"
#include "td/telegram/UpdatesManager.h"

#include "td/utils/buffer.h"
#include "td/utils/logging.h"
#include "td/utils/Status.h"

namespace td {

class SetGameScoreQuery final : public Td::ResultHandler {
  Promise<Unit> promise_;
  DialogId dialog_id_;

 public:
  explicit SetGameScoreQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
  }

  void send(DialogId dialog_id, MessageId message_id, bool edit_message,
            tl_object_ptr<telegram_api::InputUser> input_user, int32 score, bool force) {
    int32 flags = 0;
    if (edit_message) {
      flags |= telegram_api::messages_setGameScore::EDIT_MESSAGE_MASK;
    }
    if (force) {
      flags |= telegram_api::messages_setGameScore::FORCE_MASK;
    }

    dialog_id_ = dialog_id;

    auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Edit);
    if (input_peer == nullptr) {
      return on_error(Status::Error(400, "Can't access the chat"));
    }

    CHECK(input_user != nullptr);
    send_query(G()->net_query_creator().create(
        telegram_api::messages_setGameScore(flags, false /*ignored*/, false /*ignored*/, std::move(input_peer),
                                            message_id.get_server_message_id().get(), std::move(input_user), score),
        {{dialog_id}}));
  }

  void on_result(BufferSlice packet) final {
    auto result_ptr = fetch_result<telegram_api::messages_setGameScore>(packet);
    if (result_ptr.is_error()) {
      return on_error(result_ptr.move_as_error());
    }

    auto ptr = result_ptr.move_as_ok();
    LOG(INFO) << "Receive result for SetGameScore: " << to_string(ptr);
    td_->updates_manager_->on_get_updates(std::move(ptr), std::move(promise_));
  }

  void on_error(Status status) final {
    LOG(INFO) << "Receive error for SetGameScoreQuery: " << status;
    td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "SetGameScoreQuery");
    promise_.set_error(std::move(status));
  }
};

class SetInlineGameScoreQuery final : public Td::ResultHandler {
  Promise<Unit> promise_;

 public:
  explicit SetInlineGameScoreQuery(Promise<Unit> &&promise) : promise_(std::move(promise)) {
  }

  void send(tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id, bool edit_message,
            tl_object_ptr<telegram_api::InputUser> input_user, int32 score, bool force) {
    CHECK(input_bot_inline_message_id != nullptr);
    CHECK(input_user != nullptr);

    int32 flags = 0;
    if (edit_message) {
      flags |= telegram_api::messages_setInlineGameScore::EDIT_MESSAGE_MASK;
    }
    if (force) {
      flags |= telegram_api::messages_setInlineGameScore::FORCE_MASK;
    }

    auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id));
    send_query(G()->net_query_creator().create(
        telegram_api::messages_setInlineGameScore(flags, false /*ignored*/, false /*ignored*/,
                                                  std::move(input_bot_inline_message_id), std::move(input_user), score),
        {}, dc_id));
  }

  void on_result(BufferSlice packet) final {
    auto result_ptr = fetch_result<telegram_api::messages_setInlineGameScore>(packet);
    if (result_ptr.is_error()) {
      return on_error(result_ptr.move_as_error());
    }

    LOG_IF(ERROR, !result_ptr.ok()) << "Receive false in result of setInlineGameScore";

    promise_.set_value(Unit());
  }

  void on_error(Status status) final {
    LOG(INFO) << "Receive error for SetInlineGameScoreQuery: " << status;
    promise_.set_error(std::move(status));
  }
};

class GetGameHighScoresQuery final : public Td::ResultHandler {
  Promise<td_api::object_ptr<td_api::gameHighScores>> promise_;
  DialogId dialog_id_;

 public:
  explicit GetGameHighScoresQuery(Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise)
      : promise_(std::move(promise)) {
  }

  void send(DialogId dialog_id, MessageId message_id, tl_object_ptr<telegram_api::InputUser> input_user) {
    dialog_id_ = dialog_id;

    auto input_peer = td_->messages_manager_->get_input_peer(dialog_id, AccessRights::Read);
    CHECK(input_peer != nullptr);

    CHECK(input_user != nullptr);
    send_query(G()->net_query_creator().create(telegram_api::messages_getGameHighScores(
        std::move(input_peer), message_id.get_server_message_id().get(), std::move(input_user))));
  }

  void on_result(BufferSlice packet) final {
    auto result_ptr = fetch_result<telegram_api::messages_getGameHighScores>(packet);
    if (result_ptr.is_error()) {
      return on_error(result_ptr.move_as_error());
    }

    promise_.set_value(td_->game_manager_->get_game_high_scores_object(result_ptr.move_as_ok()));
  }

  void on_error(Status status) final {
    td_->messages_manager_->on_get_dialog_error(dialog_id_, status, "GetGameHighScoresQuery");
    promise_.set_error(std::move(status));
  }
};

class GetInlineGameHighScoresQuery final : public Td::ResultHandler {
  Promise<td_api::object_ptr<td_api::gameHighScores>> promise_;

 public:
  explicit GetInlineGameHighScoresQuery(Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise)
      : promise_(std::move(promise)) {
  }

  void send(tl_object_ptr<telegram_api::InputBotInlineMessageID> input_bot_inline_message_id,
            tl_object_ptr<telegram_api::InputUser> input_user) {
    CHECK(input_bot_inline_message_id != nullptr);
    CHECK(input_user != nullptr);

    auto dc_id = DcId::internal(InlineQueriesManager::get_inline_message_dc_id(input_bot_inline_message_id));
    send_query(G()->net_query_creator().create(
        telegram_api::messages_getInlineGameHighScores(std::move(input_bot_inline_message_id), std::move(input_user)),
        {}, dc_id));
  }

  void on_result(BufferSlice packet) final {
    auto result_ptr = fetch_result<telegram_api::messages_getInlineGameHighScores>(packet);
    if (result_ptr.is_error()) {
      return on_error(result_ptr.move_as_error());
    }

    promise_.set_value(td_->game_manager_->get_game_high_scores_object(result_ptr.move_as_ok()));
  }

  void on_error(Status status) final {
    promise_.set_error(std::move(status));
  }
};

GameManager::GameManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
}

GameManager::~GameManager() = default;

void GameManager::tear_down() {
  parent_.reset();
}

void GameManager::set_game_score(FullMessageId full_message_id, bool edit_message, UserId user_id, int32 score,
                                 bool force, Promise<td_api::object_ptr<td_api::message>> &&promise) {
  CHECK(td_->auth_manager_->is_bot());

  if (!td_->messages_manager_->have_message_force(full_message_id, "set_game_score")) {
    return promise.set_error(Status::Error(400, "Message not found"));
  }

  auto dialog_id = full_message_id.get_dialog_id();
  if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Edit)) {
    return promise.set_error(Status::Error(400, "Can't access the chat"));
  }

  auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
  if (r_input_user.is_error()) {
    return promise.set_error(r_input_user.move_as_error());
  }

  if (!td_->messages_manager_->can_set_game_score(full_message_id)) {
    return promise.set_error(Status::Error(400, "Game score can't be set"));
  }

  auto query_promise = PromiseCreator::lambda(
      [actor_id = actor_id(this), full_message_id, promise = std::move(promise)](Result<Unit> &&result) mutable {
        if (result.is_error()) {
          return promise.set_error(result.move_as_error());
        }
        send_closure(actor_id, &GameManager::on_set_game_score, full_message_id, std::move(promise));
      });
  td_->create_handler<SetGameScoreQuery>(std::move(query_promise))
      ->send(dialog_id, full_message_id.get_message_id(), edit_message, r_input_user.move_as_ok(), score, force);
}

void GameManager::on_set_game_score(FullMessageId full_message_id,
                                    Promise<td_api::object_ptr<td_api::message>> &&promise) {
  promise.set_value(td_->messages_manager_->get_message_object(full_message_id, "on_set_game_score"));
}

void GameManager::set_inline_game_score(const string &inline_message_id, bool edit_message, UserId user_id, int32 score,
                                        bool force, Promise<Unit> &&promise) {
  CHECK(td_->auth_manager_->is_bot());

  auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
  if (input_bot_inline_message_id == nullptr) {
    return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
  }

  auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
  if (r_input_user.is_error()) {
    return promise.set_error(r_input_user.move_as_error());
  }

  td_->create_handler<SetInlineGameScoreQuery>(std::move(promise))
      ->send(std::move(input_bot_inline_message_id), edit_message, r_input_user.move_as_ok(), score, force);
}

void GameManager::get_game_high_scores(FullMessageId full_message_id, UserId user_id,
                                       Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise) {
  CHECK(td_->auth_manager_->is_bot());

  if (!td_->messages_manager_->have_message_force(full_message_id, "get_game_high_scores")) {
    return promise.set_error(Status::Error(400, "Message not found"));
  }

  auto dialog_id = full_message_id.get_dialog_id();
  if (!td_->messages_manager_->have_input_peer(dialog_id, AccessRights::Read)) {
    return promise.set_error(Status::Error(400, "Can't access the chat"));
  }
  auto message_id = full_message_id.get_message_id();
  if (message_id.is_scheduled() || !message_id.is_server()) {
    return promise.set_error(Status::Error(400, "Wrong message identifier specified"));
  }

  auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
  if (r_input_user.is_error()) {
    return promise.set_error(r_input_user.move_as_error());
  }

  td_->create_handler<GetGameHighScoresQuery>(std::move(promise))
      ->send(dialog_id, message_id, r_input_user.move_as_ok());
}

void GameManager::get_inline_game_high_scores(const string &inline_message_id, UserId user_id,
                                              Promise<td_api::object_ptr<td_api::gameHighScores>> &&promise) {
  CHECK(td_->auth_manager_->is_bot());

  auto input_bot_inline_message_id = td_->inline_queries_manager_->get_input_bot_inline_message_id(inline_message_id);
  if (input_bot_inline_message_id == nullptr) {
    return promise.set_error(Status::Error(400, "Invalid inline message identifier specified"));
  }

  auto r_input_user = td_->contacts_manager_->get_input_user(user_id);
  if (r_input_user.is_error()) {
    return promise.set_error(r_input_user.move_as_error());
  }

  td_->create_handler<GetInlineGameHighScoresQuery>(std::move(promise))
      ->send(std::move(input_bot_inline_message_id), r_input_user.move_as_ok());
}

td_api::object_ptr<td_api::gameHighScores> GameManager::get_game_high_scores_object(
    telegram_api::object_ptr<telegram_api::messages_highScores> &&high_scores) {
  td_->contacts_manager_->on_get_users(std::move(high_scores->users_), "get_game_high_scores_object");

  auto result = td_api::make_object<td_api::gameHighScores>();
  for (const auto &high_score : high_scores->scores_) {
    int32 position = high_score->pos_;
    UserId user_id(high_score->user_id_);
    int32 score = high_score->score_;
    if (position <= 0 || !user_id.is_valid() || score < 0) {
      LOG(ERROR) << "Receive wrong " << to_string(high_score);
      continue;
    }
    result->scores_.push_back(make_tl_object<td_api::gameHighScore>(
        position, td_->contacts_manager_->get_user_id_object(user_id, "get_game_high_scores_object"), score));
  }
  return result;
}

}  // namespace td
