#include "api_url_parser.hpp"

#include <cassert>
#include <string_view>

namespace {

// RFC 3986 hex digit to value conversion
// Returns -1 for invalid hex digits
int hex_digit_to_value(char c) {
  if (c >= '0' && c <= '9')
    return c - '0';
  if (c >= 'A' && c <= 'F')
    return c - 'A' + 10;
  if (c >= 'a' && c <= 'f')
    return c - 'a' + 10;
  return -1;
}

// Decode percent-encoded sequence at src
// Returns decoded byte value, or -1 for malformed encoding
int decode_percent_sequence(const char *src) {
  if (src[0] != '%')
    return -1;

  int high = hex_digit_to_value(src[1]);
  int low = hex_digit_to_value(src[2]);

  if (high == -1 || low == -1)
    return -1;

  return (high << 4) | low;
}

// Decode RFC 3986 percent-encoding in place (for path segments)
// Returns new length, or -1 for malformed encoding
int decode_path_segment(char *data, int length) {
  char *write_pos = data;
  const char *read_pos = data;
  const char *end = data + length;

  while (read_pos < end) {
    if (*read_pos == '%') {
      if (read_pos + 2 >= end)
        return -1; // Incomplete sequence

      int decoded = decode_percent_sequence(read_pos);
      if (decoded == -1)
        return -1; // Malformed sequence

      *write_pos++ = static_cast<char>(decoded);
      read_pos += 3;
    } else {
      *write_pos++ = *read_pos++;
    }
  }

  return static_cast<int>(write_pos - data);
}

// Decode application/x-www-form-urlencoded in place (for query parameters)
// Handles + → space conversion, then percent-decoding
// Returns new length, or -1 for malformed encoding
int decode_query_value(char *data, int length) {
  char *write_pos = data;
  const char *read_pos = data;
  const char *end = data + length;

  while (read_pos < end) {
    if (*read_pos == '+') {
      *write_pos++ = ' ';
      read_pos++;
    } else if (*read_pos == '%') {
      if (read_pos + 2 >= end)
        return -1; // Incomplete sequence

      int decoded = decode_percent_sequence(read_pos);
      if (decoded == -1)
        return -1; // Malformed sequence

      *write_pos++ = static_cast<char>(decoded);
      read_pos += 3;
    } else {
      *write_pos++ = *read_pos++;
    }
  }

  return static_cast<int>(write_pos - data);
}

// A simplified helper to find a delimiter in a buffer
// Returns the position of the delimiter, or -1 if not found
int find_delimiter(const char *data, int length, char delimiter) {
  for (int i = 0; i < length; ++i) {
    if (data[i] == delimiter) {
      return i;
    }
  }
  return -1;
}

// Maps a string parameter key to its corresponding enum value.
// Unrecognized keys are ignored as per the design.
[[nodiscard]] std::optional<ApiParameterKey>
to_api_parameter_key(std::string_view key) {
  if (key == "request_id")
    return ApiParameterKey::RequestId;
  if (key == "min_version")
    return ApiParameterKey::MinVersion;
  return std::nullopt;
}

// Parses the query string part of a URL (in-place decoding)
// Returns ParseResult::Success or ParseResult::MalformedEncoding
ParseResult parse_query_string(char *query_data, int query_length,
                               RouteMatch &match) {
  int pos = 0;

  while (pos < query_length) {
    // Find end of current key=value pair
    int pair_end = find_delimiter(query_data + pos, query_length - pos, '&');
    if (pair_end == -1)
      pair_end = query_length - pos;

    // Find = separator within the pair
    int eq_pos = find_delimiter(query_data + pos, pair_end, '=');
    if (eq_pos == -1) {
      // No value, skip this parameter
      pos += pair_end + 1;
      continue;
    }

    // Decode key and value in place
    char *key_start = query_data + pos;
    int key_length = eq_pos;

    char *value_start = query_data + pos + eq_pos + 1;
    int value_length = pair_end - eq_pos - 1;

    // Decode value (query parameters use form encoding)
    int decoded_value_length = decode_query_value(value_start, value_length);
    if (decoded_value_length == -1) {
      return ParseResult::MalformedEncoding;
    }

    // Check if this is a parameter we care about
    std::string_view key_view(key_start, key_length);
    if (auto key_enum = to_api_parameter_key(key_view)) {
      match.params[static_cast<int>(*key_enum)] =
          std::string_view(value_start, decoded_value_length);
    }

    pos += pair_end + 1;
  }

  return ParseResult::Success;
}

} // namespace

ParseResult ApiUrlParser::parse(std::string_view method, char *url_data,
                                int url_length, RouteMatch &result) {
  assert(url_data != nullptr);
  assert(url_length >= 0);

  // Find query string separator
  int query_start = find_delimiter(url_data, url_length, '?');

  char *path_data = url_data;
  int path_length = (query_start == -1) ? url_length : query_start;

  char *query_data = (query_start == -1) ? nullptr : url_data + query_start + 1;
  int query_length = (query_start == -1) ? 0 : url_length - query_start - 1;

  // Parse and decode query string first
  if (query_data && query_length > 0) {
    ParseResult query_result =
        parse_query_string(query_data, query_length, result);
    if (query_result != ParseResult::Success) {
      return query_result;
    }
  }

  // Decode path segment (RFC 3986 percent-decoding)
  int decoded_path_length = decode_path_segment(path_data, path_length);
  if (decoded_path_length == -1) {
    return ParseResult::MalformedEncoding;
  }

  std::string_view path(path_data, decoded_path_length);

  // Route matching with decoded path
  if (method == "GET") {
    if (path == "/v1/version") {
      result.route = HttpRoute::GetVersion;
      return ParseResult::Success;
    }
    if (path == "/v1/subscribe") {
      result.route = HttpRoute::GetSubscribe;
      return ParseResult::Success;
    }
    if (path == "/v1/status") {
      result.route = HttpRoute::GetStatus;
      return ParseResult::Success;
    }
    if (path.starts_with("/v1/retention")) {
      result.route = HttpRoute::GetRetention;
      // Note: This matches both /v1/retention and /v1/retention/{id}
      if (path.length() > 13) { // length of "/v1/retention"
        std::string_view policy_id =
            path.substr(14); // length of "/v1/retention/"
        if (!policy_id.empty()) {
          result.params[static_cast<int>(ApiParameterKey::PolicyId)] =
              policy_id;
        }
      }
      return ParseResult::Success;
    }
    if (path == "/metrics") {
      result.route = HttpRoute::GetMetrics;
      return ParseResult::Success;
    }
    if (path == "/ok") {
      result.route = HttpRoute::GetOk;
      return ParseResult::Success;
    }
  } else if (method == "POST") {
    if (path == "/v1/commit") {
      result.route = HttpRoute::PostCommit;
      return ParseResult::Success;
    }
  } else if (method == "PUT") {
    if (path.starts_with("/v1/retention/")) {
      result.route = HttpRoute::PutRetention;
      std::string_view policy_id = path.substr(14);
      result.params[static_cast<int>(ApiParameterKey::PolicyId)] = policy_id;
      return ParseResult::Success;
    }
  } else if (method == "DELETE") {
    if (path.starts_with("/v1/retention/")) {
      result.route = HttpRoute::DeleteRetention;
      std::string_view policy_id = path.substr(14);
      result.params[static_cast<int>(ApiParameterKey::PolicyId)] = policy_id;
      return ParseResult::Success;
    }
  }

  result.route = HttpRoute::NotFound;
  return ParseResult::Success;
}
