#include "include/client/API.h"

#include <QDebug>

using namespace binom;

//======================================== Layered router of actions

void API::routeOpenLayer(binom::Variable data) {
  std::clog << "Incoming data: " << data << '\n';
  switch (ServerAction(data["act"].getValue().asUi8())) {
    default: return invalidActionCode();
    case ServerAction::key_exchange: return saveServerKey(std::move(data));
    case ServerAction::encrypted: return gotoSecuredLayer(std::move(data));
    case ServerAction::err: return processError(std::move(data));
  }
}

void API::routeSecuredLayer(Variable data) {
  std::clog << "Decrypted data: " << data << '\n';
  switch (ServerAction(data["act"].getValue().asUi8())) {
    default: return invalidActionCode();
    case ServerAction::server_info: return processServerInfo(std::move(data));
    case ServerAction::authentication_success: return processAuthentication(std::move(data));
    case ServerAction::request_result: return processRequestResult(std::move(data));
  }
}


void API::processRequestResult(Variable data) {
  try {
    switch (RequestCode request_code = RequestCode(data["request_type"].getValue().asUi8()); request_code) {
      case RequestCode::get_user_profiles: {
        FileNodeVisitor node = db;
        if(!node.contains("session")) {
          emit recivedError("Request result processing error!");
          return;
        } else node("session");
        if(!node.contains("user_list")) node.insert("user_list", vobj{});
        node("user_list");
        if(!node.contains("last_key")) node.insert("last_key", data["data"]["last_key"]);
        else node["last_key"].setVariable(data["last_key"]);
        if(!node.contains("list")) node.insert("list", data["data"]["list"]);
        else {
          auto user_list = node["list"];
          for(Variable& element : data["data"]["list"].toArray())
            user_list.pushBack(element);
        }
        emit userListChanged(getUserList());
      } return;
      case RequestCode::get_user_public_key: {
      } return;
      case RequestCode::get_messages: {
      } return;
      case RequestCode::get_posts: {
      } return;
    }
  }  catch (...) {
    emit recivedError("Request result processing error!");
  }
}

void API::invalidActionCode() {emit recivedError("Invalid server action code");}

//======================================== Open layer actions

void API::saveServerKey(Variable data) {
  transport_aes256_key = Security::getSecret(data["key"].toBufferArray().toByteArray(), transport_ecdh_key_pair);
  try {
    if(server_node.isNull()) {
      emit recivedError("Invalid connection!");
      disconnect();
      return;
    }
    server_node.setVariable("server_info", Variable::deserialize(
                   Security::decrypt(data["server_info"].toBufferArray().toByteArray(), transport_aes256_key)
                 ));
    if(auto active_session = db["active_session"]; active_session.getElementCount() >= 2)
      tryAuthenticateByUUID();
  } catch(const Exception& e) {
    transport_aes256_key.clear();
    emit recivedError("Key exchnage error!");
    return;
  }

  emit connectionSecureStateChanged(true);
  tryAuthenticateByUUID();
}

void API::processError(Variable data) {
  try{
    if(!data.contains("err")) {
      emit recivedError("Server error handling error!");
      return;
    }
    switch (ServerError(data["err"].getValue().asUi8())) {
      case ServerError::cryptographic_error:
        emit recivedError("Server-side cryptographic error!");
      return;

      case ServerError::authentication_failed:
        private_aes256_key.clear();
        emit recivedError("Authentication faild!");
      return;
      case ServerError::invalid_action_code:
        emit recivedError("Invalid action code!");
      return;
      case ServerError::request_handling_error:
        emit recivedError("Request handling error");
      return;
    }
  } catch(...) {
    emit recivedError("Server error handling error!");
    return;
  }
}

void API::processServerInfo(Variable data) {
  FileNodeVisitor node = db.getRoot()("session");
  if(!node.isNull()) {
    node.setVariable("server_info",
                     vobj{
                       {"name", data["name"]},
                       {"description", data["description"]}
                     });
    emit serverInfoChanged(getServerInfo());
  }
}

//======================================== Secured layer actions

void API::processAuthentication(Variable data) {
  try {
    std::clog << data << '\n';
    if(data.contains("key")) {
      if(private_aes256_key.isEmpty()) {
        emit recivedError("Auth process error!");
        return;
      }
      if(account_ecdh_key_pair) Security::freeKey(account_ecdh_key_pair);
      account_ecdh_key_pair =
          Security::getKeyPair(
            Security::decrypt(data["key"]["enc_priv"].toBufferArray().toByteArray(), private_aes256_key),
            data["key"]["pub"].toBufferArray().toByteArray()
          );
    }

    auto login_bfr = db["active_session"].getVariable(1).toBufferArray();
//    db["active_session"].setVariable(1, login_bfr);
    account_node = server_node("account_list")(login_bfr);
    account_node.setVariable("key", BufferArray(ValType::byte, &private_aes256_key, sizeof (private_aes256_key)));
    account_node.setVariable("profile", data["profile"]);
    if(data.contains("uuid")) account_node.setVariable("uuid", data["uuid"]);

    emit authenticationStateChanged(true);
    emit profileChanged(getProfile());
  }  catch (...) {
    emit recivedError("Auth process error!");
    return;
  }
}

//======================================== Layer gates

void API::gotoSecuredLayer(Variable data) {
  try{
    if(transport_aes256_key.isEmpty()) {
      emit recivedError("Decryption error!");
      return;
    }

    data =
        Variable::deserialize(
          Security::decrypt(
            data["content"].toBufferArray().toByteArray(),
          transport_aes256_key
          )
        );
    return routeSecuredLayer(std::move(data));
  } catch(...) {
    emit recivedError("Decryption error!");
    return;
  }
}
