/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

#include "platform/networkstrate/consensus_manager.h"

#include <glog/logging.h>
#include <unistd.h>

#include "platform/proto/broadcast.pb.h"

namespace resdb {

namespace {

bool ReplicaExisted(const ReplicaInfo& replica_info,
                    const std::vector<ReplicaInfo>& replicas) {
  for (auto& rep : replicas) {
    if (rep.id() == replica_info.id()) {
      return true;
    }
  }
  return false;
}

}  // namespace

ConsensusManager::ConsensusManager(const ResDBConfig& config)
    : config_(config), global_stats_(Stats::GetGlobalStats()) {
  if (config_.SignatureVerifierEnabled()) {
    verifier_ = std::make_unique<SignatureVerifier>(
        config_.GetPrivateKey(), config_.GetPublicKeyCertificateInfo());
  }
  bc_client_ = GetReplicaClient(config_.GetReplicaInfos(), true);
}

ConsensusManager::~ConsensusManager() {
  bc_client_.reset();
  Stop();
}

void ConsensusManager::UpdateBroadCastClient() {
  bc_client_ = GetReplicaClient(GetReplicas(), true);
}

ReplicaCommunicator* ConsensusManager::GetBroadCastClient() {
  return bc_client_.get();
}

SignatureVerifier* ConsensusManager::GetSignatureVerifier() {
  return verifier_ == nullptr ? nullptr : verifier_.get();
}

bool ConsensusManager::IsReady() const { return is_ready_; }

void ConsensusManager::Stop() {
  ServiceInterface::Stop();
  if (heartbeat_thread_.joinable()) {
    heartbeat_thread_.join();
  }
}

void ConsensusManager::Start() {
  ServiceInterface::Start();
  if (config_.HeartBeatEnabled() && verifier_) {
    heartbeat_thread_ =
        std::thread(&ConsensusManager::HeartBeat, this);  // pass by reference
  }
}

// Keep Boardcast the public keys to others.
void ConsensusManager::HeartBeat() {
  LOG(INFO) << "heart beat start";
  int sleep_time = 1;
  std::mutex mutex;
  std::condition_variable cv;
  while (IsRunning()) {
    {
      std::unique_lock<std::mutex> lk(hb_mutex_);
      SendHeartBeat();
    }
    std::unique_lock<std::mutex> lk(mutex);
    cv.wait_for(lk, std::chrono::microseconds(sleep_time * 1000000),
                [&] { return !IsRunning(); });
    if (is_ready_) {
      if (config_.IsTestMode()) {
        sleep_time = 1;
      } else {
        sleep_time = 60;
      }
    }
  }
}

void ConsensusManager::SendHeartBeat() {
  auto keys = verifier_->GetAllPublicKeys();

  std::vector<ReplicaInfo> replicas = GetAllReplicas();
  LOG(ERROR) << "all replicas:" << replicas.size();
  std::vector<ReplicaInfo> client_replicas = GetClientReplicas();
  HeartBeatInfo hb_info;
  hb_info.set_sender(config_.GetSelfInfo().id());
  hb_info.set_ip(config_.GetSelfInfo().ip());
  hb_info.set_port(config_.GetSelfInfo().port());
  hb_info.set_hb_version(version_);
  for (const auto& key : keys) {
    *hb_info.add_public_keys() = key;
    hb_info.add_node_version(hb_[key.public_key_info().node_id()]);
  }
  for (const auto& client : client_replicas) {
    replicas.push_back(client);
  }
  auto client = GetReplicaClient(replicas, false);
  if (client == nullptr) {
    return;
  }

  // If it is not a client node, broadcost the current primary to the client.
  if (config_.GetPublicKeyCertificateInfo()
          .public_key()
          .public_key_info()
          .type() == CertificateKeyInfo::REPLICA) {
    hb_info.set_primary(GetPrimary());
    hb_info.set_version(GetVersion());
  }
  LOG(ERROR) << " server:" << config_.GetSelfInfo().id() << " sends HB"
             << " is ready:" << is_ready_
             << " client size:" << client_replicas.size()
             << " svr size:" << replicas.size()
             << "  primary:" << hb_info.primary()
             << " version:" << hb_info.version();

  Request request;
  request.set_type(Request::TYPE_HEART_BEAT);
  request.mutable_region_info()->set_region_id(
      config_.GetConfigData().self_region_id());
  hb_info.SerializeToString(request.mutable_data());

  int ret = client->SendHeartBeat(request);
  if (ret <= 0) {
    LOG(ERROR) << " server:" << config_.GetSelfInfo().id()
               << " sends HB fail:" << ret;
  }
}

// Porcess the packages received from the network.
// context contains the client socket which can be used for sending response to
// the client, the signature for the request will be filled inside the context
// when parsing the message.
int ConsensusManager::Process(std::unique_ptr<Context> context,
                              std::unique_ptr<DataInfo> request_info) {
  global_stats_->IncClientCall();
  // Decode the whole message, it includes the certificate and data.
  ResDBMessage message;
  if (!message.ParseFromArray(request_info->buff, request_info->data_len)) {
    LOG(ERROR) << "parse data info fail";
    return -1;
  }

  std::unique_ptr<Request> request = std::make_unique<Request>();
  if (!request->ParseFromString(message.data())) {
    LOG(ERROR) << "parse data info fail";
    return -1;
  }

  if (request->type() == Request::TYPE_HEART_BEAT) {
    return Dispatch(std::move(context), std::move(request));
  }

  // Check if the certificate is valid.
  if (message.has_signature() && verifier_) {
    bool valid = verifier_->VerifyMessage(message.data(), message.signature());
    if (!valid) {
      LOG(ERROR) << "request is not valid:"
                 << message.signature().DebugString();
      LOG(ERROR) << " msg:" << message.data().size()
                 << " is recovery:" << request->is_recovery();
      return -2;
    }
  } else {
  }

  // forward the signature to the request so that it can be included in the
  // request/response set if needed.
  context->signature = message.signature();
  // LOG(ERROR) << "======= server:" << config_.GetSelfInfo().id()
  //          << " get request type:" << request->type()
  //         << " from:" << request->sender_id();

  return Dispatch(std::move(context), std::move(request));
}

// Dispatch the request if it is a heart beat message from other replica or a
// cert notification from clients. Otherwise, forward to the worker.
int ConsensusManager::Dispatch(std::unique_ptr<Context> context,
                               std::unique_ptr<Request> request) {
  if (request->type() == Request::TYPE_HEART_BEAT) {
    return ProcessHeartBeat(std::move(context), std::move(request));
  }
  return ConsensusCommit(std::move(context), std::move(request));
}

int ConsensusManager::ProcessHeartBeat(std::unique_ptr<Context> context,
                                       std::unique_ptr<Request> request) {
  std::unique_lock<std::mutex> lk(hb_mutex_);
  std::vector<ReplicaInfo> replicas = GetReplicas();
  HeartBeatInfo hb_info;
  if (!hb_info.ParseFromString(request->data())) {
    LOG(ERROR) << "parse replica info fail\n";
    return -1;
  }

  LOG(ERROR) << "receive public size:" << hb_info.public_keys().size()
             << " primary:" << hb_info.primary()
             << " version:" << hb_info.version()
             << " from region:" << request->region_info().region_id()
             << " sender:" << hb_info.sender()
             << " last send:" << hb_info.hb_version()
             << " current v:" << hb_[hb_info.sender()];

  if (request->region_info().region_id() ==
      config_.GetConfigData().self_region_id()) {
    if (config_.GetPublicKeyCertificateInfo()
            .public_key()
            .public_key_info()
            .type() == CertificateKeyInfo::CLIENT) {
      // TODO count 2f+1 before setting a new primary
      SetPrimary(hb_info.primary(), hb_info.version());
    }
  }

  int replica_num = 0;
  // Update the public keys received from others.
  for (const auto& public_key : hb_info.public_keys()) {
    if (verifier_ && !verifier_->AddPublicKey(public_key)) {
      LOG(ERROR) << "set public key fail from:"
                 << public_key.public_key_info().node_id();
      continue;
    }
    if (request->region_info().region_id() !=
        config_.GetConfigData().self_region_id()) {
      // LOG(ERROR) << "key from other region:"
      //           << request->region_info().region_id();
      continue;
    }

    ReplicaInfo info;
    info.set_ip(public_key.public_key_info().ip());
    info.set_port(public_key.public_key_info().port());
    info.set_id(public_key.public_key_info().node_id());
    if (info.ip().empty()) {
      LOG(ERROR) << "public doesn't have ip, skip";
      continue;
    }
    // Check whether there is a new replica joining.
    // TODO notify new replica
    if (public_key.public_key_info().type() == CertificateKeyInfo::REPLICA) {
      replica_num++;
      if (!ReplicaExisted(info, replicas)) {
        // AddNewReplica(info);
      }
    } else {
      if (!ReplicaExisted(info, clients_)) {
        AddNewClient(info);
      }
    }
  }

  if (!hb_info.ip().empty() && hb_info.hb_version() > 0 &&
      hb_[hb_info.sender()] != hb_info.hb_version()) {
    ReplicaInfo info;
    info.set_ip(hb_info.ip());
    info.set_port(hb_info.port());
    info.set_id(hb_info.sender());
    // bc_client_->Flush(info);
    hb_[hb_info.sender()] = hb_info.hb_version();
    SendHeartBeat();
  }

  if (!is_ready_ && replica_num >= config_.GetMinDataReceiveNum()) {
    LOG(ERROR) << "============ Server " << config_.GetSelfInfo().id()
               << " is ready "
                  "=====================";
    is_ready_ = true;
  }
  return 0;
}

int ConsensusManager::ConsensusCommit(std::unique_ptr<Context> context,
                                      std::unique_ptr<Request> request) {
  return -1;
}

std::vector<ReplicaInfo> ConsensusManager::GetClientReplicas() {
  return clients_;
}

std::vector<ReplicaInfo> ConsensusManager::GetAllReplicas() {
  auto config_data = config_.GetConfigData();
  std::vector<ReplicaInfo> ret;
  for (const auto& r : config_data.region()) {
    for (const auto& replica : r.replica_info()) {
      ret.push_back(replica);
    }
  }
  return ret;
}

void ConsensusManager::BroadCast(const Request& request) {
  int ret = bc_client_->SendMessage(request);
  if (ret < 0) {
    LOG(ERROR) << "broadcast request fail:";
  }
}

void ConsensusManager::SendMessage(const google::protobuf::Message& message,
                                   int64_t node_id) {
  std::vector<ReplicaInfo> replicas = GetReplicas();
  ReplicaInfo target_replica;
  for (const auto& replica : replicas) {
    if (replica.id() == node_id) {
      target_replica = replica;
      break;
    }
  }

  if (target_replica.ip().empty()) {
    return;
  }

  int ret = bc_client_->SendMessage(message, target_replica);
  if (ret < 0) {
    LOG(ERROR) << "broadcast request fail:";
  }
}

std::unique_ptr<ReplicaCommunicator> ConsensusManager::GetReplicaClient(
    const std::vector<ReplicaInfo>& replicas, bool is_use_long_conn) {
  return std::make_unique<ReplicaCommunicator>(
      replicas,
      verifier_ == nullptr || config_.GetConfigData().not_need_signature()
          ? nullptr
          : verifier_.get(),
      is_use_long_conn, config_.GetOutputWorkerNum(), config_.GetTcpBatchNum());
}

void ConsensusManager::AddNewReplica(const ReplicaInfo& info) {}

void ConsensusManager::AddNewClient(const ReplicaInfo& info) {
  clients_.push_back(info);
  bc_client_->UpdateClientReplicas(clients_);
}

void ConsensusManager::SetPrimary(uint32_t primary, uint64_t version) {}

uint32_t ConsensusManager::GetPrimary() { return 1; }

uint32_t ConsensusManager::GetVersion() { return 1; }

}  // namespace resdb
