#include "config.h"

#include <cstring>

#include <torrent/exceptions.h>
#include <torrent/utils/string_manip.h>

#include "parse_commands.h"
#include "rpc/rpc_manager.h"

namespace rpc {

CommandMap commands;
RpcManager rpc;
ExecFile   execFile;

// Trusted/untrusted XMLRPC connection model.
//
// The trust state is set per-request by the SCGI layer based on the
// UNTRUSTED_CONNECTION header. Commands without flag_untrusted_safe
// are blocked for untrusted connections. The check is in
// CommandMap::call_command(), which catches all command execution
// including nested calls through argument expansion.

bool
RpcManager::is_trusted() const {
  return m_trusted;
}

void
RpcManager::object_to_target(const torrent::Object& obj, int call_flags, rpc::target_type* target, std::function<void()>* deleter) {
  if (!obj.is_string())
    throw torrent::input_error("invalid parameters: target must be a string");

  std::string target_string = obj.as_string();
  bool        require_index = (call_flags & (CommandMap::flag_tracker_target | CommandMap::flag_file_target));

  if (target_string.size() == 0 && !require_index)
    return;

  // Length of SHA1 hash is 40
  if (target_string.size() < 40)
    throw torrent::input_error("invalid parameters: invalid target");

  char        type = 'd';
  std::string hash;
  std::string index;

  const auto& delim_pos = target_string.find_first_of(':', 40);

  if (delim_pos == target_string.npos || delim_pos + 2 >= target_string.size()) {
    if (require_index) {
      throw torrent::input_error("invalid parameters: no index");
    }

    hash = target_string;

  } else {
    hash  = target_string.substr(0, delim_pos);
    type  = target_string[delim_pos + 1];
    index = target_string.substr(delim_pos + 2);
  }

  core::Download* download = rpc.slot_find_download()(hash.c_str());

  if (download == nullptr)
    throw torrent::input_error("invalid parameters: info-hash not found");

  try {
    torrent::tracker::Tracker* tracker{};

    switch (type) {
    case 'd':
      *target = rpc::make_target(download);
      break;

    case 'f':
      *target = rpc::make_target(command_base::target_file,
                                 rpc.slot_find_file()(download, std::stoi(std::string(index))));

      break;

    case 't':
      tracker = new torrent::tracker::Tracker(rpc.slot_find_tracker()(download, std::stoi(std::string(index))));

      *target = rpc::make_target(command_base::target_tracker, tracker);
      *deleter = [tracker]() { delete tracker; };
      break;

    case 'p': {
      if (index.size() != 40)
        throw torrent::input_error("invalid parameters: target is not 40 bytes long");

      torrent::HashString hash;

      if (torrent::utils::transform_from_hex(index.c_str(), index.c_str() + 40, hash) != hash.end())
        throw torrent::input_error("invalid parameters: target is not a hex string");

      *target = rpc::make_target(command_base::target_peer, rpc.slot_find_peer()(download, hash));

      break;
    }
    default:
      throw torrent::input_error("invalid parameters: unexpected target type");
    }

  } catch (const std::logic_error&) {
    throw torrent::input_error("invalid parameters: invalid index");
  }
}

bool
RpcManager::process(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback) {
  switch (type) {
  case RPCType::XML:
    if (!m_xmlrpc.is_valid() || !m_use_xmlrpc) {
      const std::string response = "<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><i8>-501</i8></value></member><member><name>faultString</name><value><string>XML-RPC not supported</string></value></member></struct></value></fault></methodResponse>";
      return callback(response.c_str(), response.size());
    }

    return m_xmlrpc.process(in_buffer, length, callback);

  case RPCType::JSON:
    if (!m_use_jsonrpc) {
      const std::string response = "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"JSON-RPC not supported\"},\"id\":null}";
      return callback(response.c_str(), response.size());
    }

    return m_jsonrpc.process(in_buffer, length, callback);

  default:
    throw torrent::input_error("invalid parameters: unknown RPC type");
  }
}

bool
RpcManager::process_untrusted(RPCType type, const char* in_buffer, uint32_t length, slot_response_callback callback) {
  bool previous = m_trusted;
  m_trusted = false;

  try {
    bool result = process(type, in_buffer, length, callback);
    m_trusted = previous;
    return result;
  } catch (...) {
    m_trusted = previous;
    throw;
  }
}

void
RpcManager::initialize_handlers() {
  if (m_handlers_initialized)
    throw torrent::internal_error("handlers already initialized");

  m_xmlrpc.initialize();
  m_jsonrpc.initialize();

  m_handlers_initialized = true;
}

void
RpcManager::cleanup() {
  m_handlers_initialized = false;

  m_xmlrpc.cleanup();
  m_jsonrpc.cleanup();
}

void
RpcManager::set_size_limit(uint64_t size) {
  if (size > SCgiTask::max_content_size)
    throw torrent::input_error("XMLRPC size limit cannot exceed the SCGI content size limit.");

  m_xmlrpc.set_size_limit(size);
}

void
RpcManager::insert_command(const char* name, const char* parm, const char* doc) {
  m_xmlrpc.insert_command(name, parm, doc);
  m_jsonrpc.insert_command(name, parm, doc);
}

void
RpcManager::mark_safe(const std::string& key) {
  auto itr = commands.find(key);

  if (itr == commands.end())
    return;

  itr->second.m_flags |= CommandMap::flag_untrusted_safe;
}

} // namespace rpc
