#include "server/handler_v2.h"

#include <gflags/gflags.h>
#include <glog/logging.h>
#include <algorithm>
#include <string>
#include <vector>

#include "log/cert.h"
#include "log/cert_checker.h"
#include "log/cluster_state_controller.h"
#include "log/log_lookup.h"
#include "log/logged_entry.h"
#include "monitoring/latency.h"
#include "monitoring/monitoring.h"
#include "server/json_output.h"
#include "server/proxy.h"
#include "util/json_wrapper.h"
#include "util/thread_pool.h"

namespace libevent = cert_trans::libevent;

using cert_trans::Counter;
using cert_trans::HttpHandlerV2;
using cert_trans::Latency;
using cert_trans::LoggedEntry;
using cert_trans::Proxy;
using cert_trans::ScopedLatency;
using ct::ShortMerkleAuditProof;
using ct::SignedCertificateTimestamp;
using ct::SignedTreeHead;
using std::bind;
using std::chrono::milliseconds;
using std::chrono::seconds;
using std::lock_guard;
using std::make_shared;
using std::multimap;
using std::min;
using std::mutex;
using std::placeholders::_1;
using std::string;
using std::unique_ptr;
using std::vector;

DEFINE_int32(max_leaf_entries_per_response, 1000,
             "maximum number of entries to put in the response of a "
             "get-entries request");

namespace {


static Latency<milliseconds, string> http_server_request_latency_ms(
    "total_http_server_request_latency_ms", "path",
    "Total request latency in ms broken down by path");


}  // namespace


HttpHandlerV2::HttpHandlerV2(LogLookup* log_lookup, const ReadOnlyDatabase* db,
                             const ClusterStateController* controller,
                             ThreadPool* pool, libevent::Base* event_base,
                             StalenessTracker* staleness_tracker)
    : log_lookup_(CHECK_NOTNULL(log_lookup)),
      db_(CHECK_NOTNULL(db)),
      controller_(CHECK_NOTNULL(controller)),
      proxy_(nullptr),
      pool_(CHECK_NOTNULL(pool)),
      event_base_(CHECK_NOTNULL(event_base)),
      staleness_tracker_(CHECK_NOTNULL(staleness_tracker)) {
}


HttpHandlerV2::~HttpHandlerV2() {
}


void StatsHandlerInterceptor(const string& path,
                             const libevent::HttpServer::HandlerCallback& cb,
                             evhttp_request* req) {
  ScopedLatency total_http_server_request_latency(
      http_server_request_latency_ms.GetScopedLatency(path));

  cb(req);
}


void HttpHandlerV2::AddEntryReply(
    evhttp_request* req, const util::Status& add_status,
    const SignedCertificateTimestamp& sct) const {
  return SendJsonError(event_base_, req, HTTP_NOTIMPLEMENTED,
                       "Not yet implemented.");
}

void HttpHandlerV2::ProxyInterceptor(
    const libevent::HttpServer::HandlerCallback& local_handler,
    evhttp_request* request) {
  VLOG(2) << "Running proxy interceptor...";
  // TODO(alcutter): We can be a bit smarter about when to proxy off
  // the request - being stale wrt to the current serving STH doesn't
  // automatically mean we're unable to answer this request.
  if (staleness_tracker_->IsNodeStale()) {
    // Can't do this on the libevent thread since it can block on the lock in
    // ClusterStatusController::GetFreshNodes().
    pool_->Add(bind(&Proxy::ProxyRequest, proxy_, request));
  } else {
    local_handler(request);
  }
}


void HttpHandlerV2::AddProxyWrappedHandler(
    libevent::HttpServer* server, const string& path,
    const libevent::HttpServer::HandlerCallback& local_handler) {
  const libevent::HttpServer::HandlerCallback stats_handler(
      bind(&StatsHandlerInterceptor, path, local_handler, _1));
  CHECK(server->AddHandler(path, bind(&HttpHandlerV2::ProxyInterceptor, this,
                                      stats_handler, _1)));
}


void HttpHandlerV2::Add(libevent::HttpServer* server) {
  CHECK_NOTNULL(server);
  // TODO(pphaneuf): An optional prefix might be nice?
  // TODO(pphaneuf): Find out which methods are CPU intensive enough
  // that they should be spun off to the thread pool.
  AddProxyWrappedHandler(server, "/ct/v2/get-entries",
                         bind(&HttpHandlerV2::GetEntries, this, _1));
  AddProxyWrappedHandler(server, "/ct/v2/get-proof-by-hash",
                         bind(&HttpHandlerV2::GetProof, this, _1));
  AddProxyWrappedHandler(server, "/ct/v2/get-sth",
                         bind(&HttpHandlerV2::GetSTH, this, _1));
  AddProxyWrappedHandler(server, "/ct/v2/get-sth-consistency",
                         bind(&HttpHandlerV2::GetConsistency, this, _1));

  // Now add any sub-class handlers.
  AddHandlers(server);
}


void HttpHandlerV2::SetProxy(Proxy* proxy) {
  LOG_IF(FATAL, proxy_) << "Attempting to re-add a Proxy.";
  proxy_ = CHECK_NOTNULL(proxy);
}


void HttpHandlerV2::GetEntries(evhttp_request* req) const {
  return SendJsonError(event_base_, req, HTTP_NOTIMPLEMENTED,
                       "Not yet implemented.");
}


void HttpHandlerV2::GetProof(evhttp_request* req) const {
  return SendJsonError(event_base_, req, HTTP_NOTIMPLEMENTED,
                       "Not yet implemented.");
}


void HttpHandlerV2::GetSTH(evhttp_request* req) const {
  return SendJsonError(event_base_, req, HTTP_NOTIMPLEMENTED,
                       "Not yet implemented.");
}


void HttpHandlerV2::GetConsistency(evhttp_request* req) const {
  return SendJsonError(event_base_, req, HTTP_NOTIMPLEMENTED,
                       "Not yet implemented.");
}


void HttpHandlerV2::BlockingGetEntries(evhttp_request* req, int64_t start,
                                       int64_t end, bool include_scts) const {
  return SendJsonError(event_base_, req, HTTP_NOTIMPLEMENTED,
                       "Not yet implemented.");
}
