//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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/PrivacyManager.h"

#include "td/telegram/ContactsManager.h"
#include "td/telegram/Global.h"
#include "td/telegram/net/NetQueryCreator.h"
#include "td/telegram/net/NetQueryDispatcher.h"
#include "td/telegram/Td.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"

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

#include <algorithm>
#include <iterator>

namespace td {

class GetPrivacyQuery final : public Td::ResultHandler {
  Promise<UserPrivacySettingRules> promise_;

 public:
  explicit GetPrivacyQuery(Promise<UserPrivacySettingRules> &&promise) : promise_(std::move(promise)) {
  }

  void send(UserPrivacySetting user_privacy_setting) {
    send_query(G()->net_query_creator().create(
        telegram_api::account_getPrivacy(user_privacy_setting.get_input_privacy_key())));
  }

  void on_result(BufferSlice packet) final {
    auto result_ptr = fetch_result<telegram_api::account_getPrivacy>(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 GetPrivacyQuery: " << to_string(ptr);
    promise_.set_value(UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(ptr)));
  }

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

class SetPrivacyQuery final : public Td::ResultHandler {
  Promise<UserPrivacySettingRules> promise_;

 public:
  explicit SetPrivacyQuery(Promise<UserPrivacySettingRules> &&promise) : promise_(std::move(promise)) {
  }

  void send(UserPrivacySetting user_privacy_setting, UserPrivacySettingRules &&privacy_rules) {
    send_query(G()->net_query_creator().create(telegram_api::account_setPrivacy(
        user_privacy_setting.get_input_privacy_key(), privacy_rules.get_input_privacy_rules(td_))));
  }

  void on_result(BufferSlice packet) final {
    auto result_ptr = fetch_result<telegram_api::account_setPrivacy>(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 SetPrivacyQuery: " << to_string(ptr);
    promise_.set_value(UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(ptr)));
  }

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

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

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

void PrivacyManager::get_privacy(tl_object_ptr<td_api::UserPrivacySetting> key,
                                 Promise<tl_object_ptr<td_api::userPrivacySettingRules>> promise) {
  auto r_user_privacy_setting = UserPrivacySetting::get_user_privacy_setting(std::move(key));
  if (r_user_privacy_setting.is_error()) {
    return promise.set_error(r_user_privacy_setting.move_as_error());
  }
  auto user_privacy_setting = r_user_privacy_setting.move_as_ok();
  auto &info = get_info(user_privacy_setting);
  if (info.is_synchronized_) {
    return promise.set_value(info.rules_.get_user_privacy_setting_rules_object(td_));
  }
  info.get_promises_.push_back(std::move(promise));
  if (info.get_promises_.size() > 1u) {
    // query has already been sent, just wait for the result
    return;
  }
  auto query_promise = PromiseCreator::lambda(
      [actor_id = actor_id(this), user_privacy_setting](Result<UserPrivacySettingRules> r_privacy_rules) {
        send_closure(actor_id, &PrivacyManager::on_get_user_privacy_settings, user_privacy_setting,
                     std::move(r_privacy_rules));
      });
  td_->create_handler<GetPrivacyQuery>(std::move(query_promise))->send(user_privacy_setting);
}

void PrivacyManager::set_privacy(tl_object_ptr<td_api::UserPrivacySetting> key,
                                 tl_object_ptr<td_api::userPrivacySettingRules> rules, Promise<Unit> promise) {
  TRY_RESULT_PROMISE(promise, user_privacy_setting, UserPrivacySetting::get_user_privacy_setting(std::move(key)));
  TRY_RESULT_PROMISE(promise, privacy_rules,
                     UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(rules)));

  auto &info = get_info(user_privacy_setting);
  if (info.has_set_query_) {
    info.pending_rules_ = std::move(privacy_rules);
    info.set_promises_.push_back(std::move(promise));
    return;
  }
  info.has_set_query_ = true;

  set_privacy_impl(user_privacy_setting, std::move(privacy_rules), std::move(promise));
}

void PrivacyManager::set_privacy_impl(UserPrivacySetting user_privacy_setting, UserPrivacySettingRules &&privacy_rules,
                                      Promise<Unit> &&promise) {
  auto query_promise =
      PromiseCreator::lambda([actor_id = actor_id(this), user_privacy_setting,
                              promise = std::move(promise)](Result<UserPrivacySettingRules> r_privacy_rules) mutable {
        send_closure(actor_id, &PrivacyManager::on_set_user_privacy_settings, user_privacy_setting,
                     std::move(r_privacy_rules), std::move(promise));
      });
  td_->create_handler<SetPrivacyQuery>(std::move(query_promise))->send(user_privacy_setting, std::move(privacy_rules));
}

void PrivacyManager::on_update_privacy(tl_object_ptr<telegram_api::updatePrivacy> update) {
  CHECK(update != nullptr);
  CHECK(update->key_ != nullptr);
  UserPrivacySetting user_privacy_setting(*update->key_);
  auto privacy_rules = UserPrivacySettingRules::get_user_privacy_setting_rules(td_, std::move(update->rules_));
  do_update_privacy(user_privacy_setting, std::move(privacy_rules), true);
}

void PrivacyManager::on_get_user_privacy_settings(UserPrivacySetting user_privacy_setting,
                                                  Result<UserPrivacySettingRules> r_privacy_rules) {
  G()->ignore_result_if_closing(r_privacy_rules);
  auto &info = get_info(user_privacy_setting);
  auto promises = std::move(info.get_promises_);
  reset_to_empty(info.get_promises_);
  for (auto &promise : promises) {
    if (r_privacy_rules.is_error()) {
      promise.set_error(r_privacy_rules.error().clone());
    } else {
      promise.set_value(r_privacy_rules.ok().get_user_privacy_setting_rules_object(td_));
    }
  }
  if (r_privacy_rules.is_ok()) {
    do_update_privacy(user_privacy_setting, r_privacy_rules.move_as_ok(), false);
  }
}

void PrivacyManager::on_set_user_privacy_settings(UserPrivacySetting user_privacy_setting,
                                                  Result<UserPrivacySettingRules> r_privacy_rules,
                                                  Promise<Unit> &&promise) {
  if (G()->close_flag()) {
    auto &info = get_info(user_privacy_setting);
    CHECK(info.has_set_query_);
    info.has_set_query_ = false;
    auto promises = std::move(info.set_promises_);
    fail_promises(promises, Global::request_aborted_error());
    promise.set_error(Global::request_aborted_error());
    return;
  }

  auto &info = get_info(user_privacy_setting);
  CHECK(info.has_set_query_);
  info.has_set_query_ = false;
  if (r_privacy_rules.is_error()) {
    promise.set_error(r_privacy_rules.move_as_error());
  } else {
    do_update_privacy(user_privacy_setting, r_privacy_rules.move_as_ok(), true);
    promise.set_value(Unit());
  }
  if (!info.set_promises_.empty()) {
    info.has_set_query_ = true;
    auto join_promise =
        PromiseCreator::lambda([promises = std::move(info.set_promises_)](Result<Unit> &&result) mutable {
          if (result.is_ok()) {
            set_promises(promises);
          } else {
            fail_promises(promises, result.move_as_error());
          }
        });
    reset_to_empty(info.set_promises_);
    set_privacy_impl(user_privacy_setting, std::move(info.pending_rules_), std::move(join_promise));
  }
}

void PrivacyManager::do_update_privacy(UserPrivacySetting user_privacy_setting, UserPrivacySettingRules &&privacy_rules,
                                       bool from_update) {
  auto &info = get_info(user_privacy_setting);
  bool was_synchronized = info.is_synchronized_;
  info.is_synchronized_ = true;

  if (!(info.rules_ == privacy_rules)) {
    if (!G()->close_flag() && (from_update || was_synchronized)) {
      switch (user_privacy_setting.type()) {
        case UserPrivacySetting::Type::UserStatus: {
          send_closure_later(G()->contacts_manager(), &ContactsManager::on_update_online_status_privacy);

          auto old_restricted = info.rules_.get_restricted_user_ids();
          auto new_restricted = privacy_rules.get_restricted_user_ids();
          if (old_restricted != new_restricted) {
            // if a user was unrestricted, it is not received from the server anymore
            // we need to reget their online status manually
            std::vector<UserId> unrestricted;
            std::set_difference(old_restricted.begin(), old_restricted.end(), new_restricted.begin(),
                                new_restricted.end(), std::back_inserter(unrestricted),
                                [](UserId lhs, UserId rhs) { return lhs.get() < rhs.get(); });
            for (auto &user_id : unrestricted) {
              send_closure_later(G()->contacts_manager(), &ContactsManager::reload_user, user_id, Promise<Unit>(),
                                 "do_update_privacy");
            }
          }
          break;
        }
        case UserPrivacySetting::Type::UserPhoneNumber:
          send_closure_later(G()->contacts_manager(), &ContactsManager::on_update_phone_number_privacy);
          break;
        default:
          break;
      }
    }

    info.rules_ = std::move(privacy_rules);
    send_closure(
        G()->td(), &Td::send_update,
        make_tl_object<td_api::updateUserPrivacySettingRules>(user_privacy_setting.get_user_privacy_setting_object(),
                                                              info.rules_.get_user_privacy_setting_rules_object(td_)));
  }
}

}  // namespace td