#include "span.h"

#include <datadog/tags.h>
#include <opentracing/ext/tags.h>

#include <iostream>
#include <nlohmann/json.hpp>
#include <regex>
#include <string>

#include "bool.h"
#include "sample.h"
#include "span_buffer.h"
#include "tracer.h"

namespace tags = datadog::tags;
using json = nlohmann::json;

namespace datadog {
namespace opentracing {

namespace {
const std::string event_sample_rate_metric = "_dd1.sr.eausr";
}  // namespace

SpanData::SpanData(std::string type, std::string service, ot::string_view resource,
                   std::string name, uint64_t trace_id, uint64_t span_id, uint64_t parent_id,
                   int64_t start, int64_t duration, int32_t error)
    : type(type),
      service(service),
      resource(resource),
      name(name),
      trace_id(trace_id),
      span_id(span_id),
      parent_id(parent_id),
      start(start),
      duration(duration),
      error(error) {}

uint64_t SpanData::traceId() const { return trace_id; }
uint64_t SpanData::spanId() const { return span_id; }

const std::string SpanData::env() const {
  const auto &env = meta.find(tags::environment);
  if (env == meta.end()) {
    return "";
  }
  return env->second;
}

std::unique_ptr<SpanData> makeSpanData(std::string type, std::string service,
                                       ot::string_view resource, std::string name,
                                       uint64_t trace_id, uint64_t span_id, uint64_t parent_id,
                                       int64_t start) {
  return std::unique_ptr<SpanData>{
      new SpanData(type, service, resource, name, trace_id, span_id, parent_id, start, 0, 0)};
}

std::unique_ptr<SpanData> stubSpanData() { return std::unique_ptr<SpanData>{new SpanData()}; }

Span::Span(std::shared_ptr<const Logger> logger, std::shared_ptr<const Tracer> tracer,
           std::shared_ptr<SpanBuffer> buffer, TimeProvider get_time, uint64_t span_id,
           uint64_t trace_id, uint64_t parent_id, SpanContext context, TimePoint start_time,
           std::string span_service, std::string span_type, std::string span_name,
           std::string resource, std::string operation_name_override, bool legacy_obfuscation)
    : logger_(std::move(logger)),
      tracer_(std::move(tracer)),
      buffer_(std::move(buffer)),
      get_time_(get_time),
      context_(std::move(context)),
      start_time_(start_time),
      operation_name_override_(operation_name_override),
      legacy_obfuscation_(legacy_obfuscation),
      span_(makeSpanData(span_type, span_service, resource, span_name, trace_id, span_id,
                         parent_id,
                         std::chrono::duration_cast<std::chrono::nanoseconds>(
                             start_time_.absolute_time.time_since_epoch())
                             .count())),
      span_description_(std::string("[trace_id=") + std::to_string(trace_id) +
                        std::string(",span_id=") + std::to_string(span_id) + std::string("]")) {
  if (!operation_name_override.empty()) {
    span_->meta[tags::operation_name] = span_->name;
    span_->name = operation_name_override;
  }
  buffer_->registerSpan(context_);
}

Span::~Span() {
  if (!is_finished_) {
    this->Finish();
  }
}

namespace {
// Matches path segments with numbers (except things that look like versions).
// Similar to, but not the same as,
// https://github.com/datadog/dd-trace-java/blob/master/dd-trace-ot/src/main/java/datadog/opentracing/decorators/URLAsResourceName.java#L14-L16
std::regex &PATH_MIXED_ALPHANUMERICS() {
  // Don't statically initialize a complex object.
  // Thread safe as of C++11, as long as it's not reentrant.
  static std::regex r{
      "(\\/)(?:(?:([^?\\/&]*)(?:\\?[^\\/]+))|(?:(?![vV]\\d{1,2}\\/)[^\\/"
      "\\d\\?]*[\\d-]+[^\\/]*))"};
  return r;
}

// Deduce `span.error` and "error*" tag values from the set values of "error*"
// tags.
// See the error-related test SECTIONs in `span_test.cpp` for more information.
void finish_error_tags(SpanData &span) {
  const std::string error_details[] = {"error.msg", "error.stack", "error.type"};
  const auto error_tag = span.meta.find(::ot::ext::error);

  if (error_tag != span.meta.end()) {
    const auto &value = error_tag->second;
    const Tribool boolness = tribool(value);

    if (value.empty() || boolness == Tribool::False) {
      span.error = 0;
      span.meta.erase(error_tag);
      for (const auto &tag_name : error_details) {
        span.meta.erase(tag_name);
      }
      return;
    }

    span.error = 1;
    if (boolness == Tribool::Neither) {
      span.meta.emplace("error.msg", value);
    }
    span.meta.erase(error_tag);
    return;
  }

  for (const auto &tag_name : error_details) {
    if (span.meta.count(tag_name)) {
      span.error = 1;
      if (error_tag != span.meta.end()) {
        span.meta.erase(error_tag);
      }
      return;
    }
  }
}
}  // namespace

// Imperfectly audits the data in a Span, removing some things that could cause information leaks
// or cardinality issues.
// If you want to add any more steps to this function, we should use a more
// sophisticated architecture. For now, YAGNI.
void audit(bool legacy_obfuscation, SpanData *span) {
  auto http_tag = span->meta.find(ot::ext::http_url);
  if (http_tag != span->meta.end()) {
    if (legacy_obfuscation) {
      // Heavy-handed obfuscation that replaces hostname, runs of alphanumerics, fragments and
      // parameters.
      http_tag->second = std::regex_replace(http_tag->second, PATH_MIXED_ALPHANUMERICS(), "$1$2?");
    } else {
      // Just trim the parameter portion of the URL.
      http_tag->second = http_tag->second.substr(0, http_tag->second.find_first_of('?'));
    }
  }
}

void Span::FinishWithOptions(
    const ot::FinishSpanOptions & /* finish_span_options */) noexcept try {
  if (is_finished_.exchange(true)) {
    return;
  }
  std::lock_guard<std::mutex> lock{mutex_};
  // Set end time.
  auto end_time = get_time_();
  span_->duration =
      std::chrono::duration_cast<std::chrono::nanoseconds>(end_time - start_time_).count();
  // Apply special tags.
  auto tag = span_->meta.find(tags::span_type);
  if (tag != span_->meta.end()) {
    span_->type = tag->second;
    span_->meta.erase(tag);
  }
  tag = span_->meta.find(tags::resource_name);
  if (tag != span_->meta.end()) {
    span_->resource = tag->second;
    span_->meta.erase(tag);
  }
  tag = span_->meta.find(tags::service_name);
  if (tag != span_->meta.end()) {
    span_->service = tag->second;
    span_->meta.erase(tag);
  }
  finish_error_tags(*span_);
  tag = span_->meta.find(tags::analytics_event);
  if (tag != span_->meta.end()) {
    // tag->second is the JSON-serialized value of the variadic type given to SetTag.
    // An empty value means it was set, but a sample rate of zero is applied.
    // A bool value indicates a rate of 0.0 for false, or 1.0 for true.
    // A double value between 0.0 and 1.0 (inclusive) is applied as-is.
    // Other values are ignored.
    if (tag->second.empty()) {
      span_->metrics[event_sample_rate_metric] = 0.0;
    } else if (isbool(tag->second)) {
      if (stob(tag->second, false)) {
        span_->metrics[event_sample_rate_metric] = 1.0;
      } else {
        span_->metrics[event_sample_rate_metric] = 0.0;
      }
    } else {
      // Check if the value is a double between 0.0 and 1.0 (inclusive).
      try {
        double value = std::stod(tag->second);
        if (value >= 0.0 && value <= 1.0) {
          span_->metrics[event_sample_rate_metric] = value;
        }
      } catch (const std::invalid_argument &ia) {
        // Ignore invalid value.
      } catch (const std::out_of_range &oor) {
        // Ignore values not in range.
      }
    }

    span_->meta.erase(tag);
  }
  // Audit and finish span.
  audit(legacy_obfuscation_, span_.get());
  buffer_->finishSpan(std::move(span_));
  // According to the OT lifecycle, no more methods should be called on this Span. But just in case
  // let's make sure that span_ isn't nullptr. Fine line between defensive programming and voodoo.
  span_ = stubSpanData();
} catch (const std::bad_alloc &) {
  // At least don't crash.
}

void Span::SetOperationName(ot::string_view operation_name) noexcept {
  std::lock_guard<std::mutex> lock_guard{mutex_};
  if (!operation_name_override_.empty()) {
    span_->meta[tags::operation_name] = operation_name;
  } else {
    span_->name = operation_name;
  }
  span_->resource = operation_name;
}

namespace {
// Visits and serializes an arbitrarily-nested variant type. Serialisation of value types is to
// string while any composite types are expressed in JSON. eg. string("fred") -> "fred"
// vector<string>{"felicity"} -> "[\"felicity\"]"
struct VariantVisitor {
  // Populated with the final result.
  std::string &result;
  VariantVisitor(std::string &result_) : result(result_) {}

 private:
  // Only set if VariantVisitor is recursing. Unfortunately we only really need an explicit
  // distinction (and all the conditionals below) to avoid the case of a simple string being
  // serialized to "\"string\"" - which is valid JSON but very silly never-the-less.
  json *json_result = nullptr;
  VariantVisitor(std::string &result_, json *json_result_)
      : result(result_), json_result(json_result_) {}

 public:
  void operator()(bool value) const {
    if (json_result != nullptr) {
      *json_result = value;
    } else {
      result = value ? "true" : "false";
    }
  }

  void operator()(double value) const {
    if (json_result != nullptr) {
      *json_result = value;
    } else {
      result = std::to_string(value);
    }
  }

  void operator()(int64_t value) const {
    if (json_result != nullptr) {
      *json_result = value;
    } else {
      result = std::to_string(value);
    }
  }

  void operator()(uint64_t value) const {
    if (json_result != nullptr) {
      *json_result = value;
    } else {
      result = std::to_string(value);
    }
  }

  void operator()(const std::string &value) const {
    if (json_result != nullptr) {
      *json_result = value;
    } else {
      result = value;
    }
  }

  void operator()(std::nullptr_t) const {
    if (json_result != nullptr) {
      *json_result = "nullptr";
    } else {
      result = "nullptr";
    }
  }

  void operator()(const char *value) const {
    if (json_result != nullptr) {
      *json_result = value;
    } else {
      result = std::string(value);
    }
  }

  void operator()(const std::vector<ot::Value> &values) const {
    json list;
    for (auto value : values) {
      json inner;
      std::string r;
      apply_visitor(VariantVisitor{r, &inner}, value);
      list.push_back(inner);
    }
    if (json_result != nullptr) {
      // We're a list in a dict/list.
      *json_result = list;
    } else {
      // We're a root object, so dump the string.
      result = list.dump();
    }
  }

  void operator()(const std::unordered_map<std::string, ot::Value> &value) const {
    json dict;
    for (auto pair : value) {
      json inner;
      std::string r;
      apply_visitor(VariantVisitor{r, &inner}, pair.second);
      dict[pair.first] = inner;
    }
    if (json_result != nullptr) {
      // We're a dict in a dict/list.
      *json_result = dict;
    } else {
      // We're a root object, so dump the string.
      result = dict.dump();
    }
  }
};
}  // namespace

// Normalizes the tag key.
// For now:
// - ':' is replaced with '.'
// Further normalization may be done in the future, such as
// converting to lowercase, and replacing spaces and other punctuation
// with underscore.
std::string normalizeTagKey(std::string tag) {
  std::replace(tag.begin(), tag.end(), ':', '.');
  return tag;
}

void Span::SetTag(ot::string_view key, const ot::Value &value) noexcept {
  std::string k = normalizeTagKey(key);
  std::string result;
  apply_visitor(VariantVisitor{result}, value);
  {
    std::lock_guard<std::mutex> lock_guard{mutex_};
    span_->meta[k] = result;
  }

  // Normally special tags are processed at Span Finish, but this cannot be done for
  // sampling tags because if no sampling is set before the Span Finishes then one is
  // assigned immutably.
  // The sampling tags are "sampling.priority", "manual.keep" and "manual.drop".
  // Doesn't need to be in the same mutex lock as above.
  if (k == ::ot::ext::sampling_priority) {
    // https://github.com/opentracing/specification/blob/master/semantic_conventions.md#span-tags-table
    // "sampling.priority"
    try {
      std::unique_ptr<UserSamplingPriority> sampling_priority = nullptr;
      if (result != "") {
        sampling_priority = std::make_unique<UserSamplingPriority>(
            std::stoi(result) == 0 ? UserSamplingPriority::UserDrop
                                   : UserSamplingPriority::UserKeep);
      }
      setSamplingPriority(std::move(sampling_priority));
    } catch (const std::invalid_argument &ia) {
      logger_->Log(LogLevel::debug, span_->trace_id, span_->span_id,
                   "unable to parse sampling priority tag");
    } catch (const std::out_of_range &oor) {
      logger_->Log(LogLevel::debug, span_->trace_id, span_->span_id,
                   "unable to parse sampling priority tag");
    }
  } else if (k == tags::manual_keep) {
    setSamplingPriority(std::make_unique<UserSamplingPriority>(UserSamplingPriority::UserKeep));
  } else if (k == tags::manual_drop) {
    setSamplingPriority(std::make_unique<UserSamplingPriority>(UserSamplingPriority::UserDrop));
  } else if (k == tags::service_name) {
    setServiceName(result);
  }
}

void Span::SetBaggageItem(ot::string_view restricted_key, ot::string_view value) noexcept {
  context_.setBaggageItem(restricted_key, value);
}

std::string Span::BaggageItem(ot::string_view restricted_key) const noexcept {
  return context_.baggageItem(restricted_key);
}

void Span::Log(
    std::initializer_list<std::pair<ot::string_view, ot::Value>> /* fields */) noexcept {}

void Span::Log(
    ot::SystemTime /* timestamp */,
    std::initializer_list<std::pair<ot::string_view, ot::Value>> /* fields */) noexcept {}

void Span::Log(ot::SystemTime /* timestamp */,
               const std::vector<std::pair<ot::string_view, ot::Value>> & /* fields */) noexcept {}

OptionalSamplingPriority Span::setSamplingPriority(
    std::unique_ptr<UserSamplingPriority> user_priority) {
  std::lock_guard<std::mutex> lock_guard{mutex_};
  return buffer_->setSamplingPriorityFromUser(context_.traceId(), user_priority);
}

OptionalSamplingPriority Span::getSamplingPriority() const {
  std::lock_guard<std::mutex> lock_guard{mutex_};
  return buffer_->getSamplingPriority(context_.traceId());
}

void Span::setServiceName(ot::string_view service_name) {
  span_->service = service_name;
  buffer_->setServiceName(traceId(), service_name);
}

const ot::SpanContext &Span::context() const noexcept {
  std::lock_guard<std::mutex> lock_guard{mutex_};
  // First apply sampling. This concern sits more reasonably upon the destructor/Finish method - to
  // ensure that users have every chance to apply their own SamplingPriority before one is decided.
  // However, OpenTracing serializes the SpanContext from a Span *before* finishing that Span. So
  // on-Span-finishing is too late to work out whether to sample or not. Therefore, we must do it
  // here, when the context is fetched before being serialized. The disadvantage of doing this here
  // is that if anything else happens to want to get and/or serialize a SpanContext, doing so will
  // have the side-effect of locking-in a sampling decision.
  buffer_->generateSamplingPriority(span_.get());
  return context_;
}

const ot::Tracer &Span::tracer() const noexcept { return *tracer_; }

uint64_t Span::traceId() const {
  return span_->trace_id;  // Never modified, hence un-locked access.
}

uint64_t Span::spanId() const {
  return span_->span_id;  // Never modified, hence un-locked access.
}

}  // namespace opentracing
}  // namespace datadog
