/**
 * This file is part of the CernVM File System.
 */

#include "repository_tag.h"

#include <cctype>
#include <string>

#include "util/platform.h"
#include "util/string.h"

RepositoryTag::RepositoryTag(const std::string &name,
                             const std::string &description)
    : name_(name)
    , description_(description)
    , delete_tags_("")
    , auto_tag_threshold_(0) { }

/**
 * Check if tag name is of the form "generic-*"
 */
bool RepositoryTag::HasGenericName() {
  return HasPrefix(name_, "generic-", false);
}

namespace {

// True if `s` is exactly "YYYY-MM-DDTHH:MM:SS(.fff)?Z" (1-3 fractional digits),
// the timestamp shape produced by RepositoryTag::SetGenericName() and by the
// `date -u +%Y-%m-%dT%H:%M:%SZ` call in cvmfs_server_publish.sh. Only the digit
// and separator structure is checked, matching the regular expression in
// filter_auto_tags() (field ranges are not validated there either).
bool IsIso8601UtcTimestamp(const std::string &s) {
  // "YYYY-MM-DDTHH:MM:SSZ" without fractional seconds is 20 characters.
  if (s.size() < 20) {
    return false;
  }
  if (s[4] != '-' || s[7] != '-' || s[10] != 'T' || s[13] != ':' ||
      s[16] != ':') {
    return false;
  }
  const size_t digit_pos[] = {0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18};
  for (size_t k = 0; k < sizeof(digit_pos) / sizeof(digit_pos[0]); ++k) {
    if (!isdigit(static_cast<unsigned char>(s[digit_pos[k]]))) {
      return false;
    }
  }
  // Optional ".fff" (1-3 digits) follows the seconds.
  size_t pos = 19;
  if (s[pos] == '.') {
    ++pos;
    size_t fractional = 0;
    while (pos < s.size() && fractional < 3 &&
           isdigit(static_cast<unsigned char>(s[pos]))) {
      ++pos;
      ++fractional;
    }
    if (fractional == 0) {
      return false;  // a '.' must be followed by at least one digit
    }
  }
  // The timestamp must end exactly with the 'Z' UTC designator.
  return pos == s.size() - 1 && s[pos] == 'Z';
}

}  // anonymous namespace

/**
 * True if `name` is a full auto-generated tag name: "generic-<ISO8601>" or,
 * on name clashes, "generic_<n>-<ISO8601>", e.g.
 * "generic-2024-01-01T00:00:00.000Z" (see SetGenericName() and the generic tag
 * handling in cvmfs_server_publish.sh). The "<ISO8601>" part must be a complete
 * "YYYY-MM-DDTHH:MM:SS(.mmm)?Z" timestamp, so user-created tags such as
 * "generic-release" or "generic-2024-Z" are not matched and never deleted by
 * the auto-tag cleanup. This mirrors the regular expression used by the
 * non-gateway cleanup in filter_auto_tags().
 */
bool RepositoryTag::IsAutoGeneratedName(const std::string &name) {
  std::string rest;
  if (HasPrefix(name, "generic-", false)) {
    rest = name.substr(8);  // strlen("generic-")
  } else if (HasPrefix(name, "generic_", false)) {
    // Skip the numeric disambiguation suffix and the following '-'.
    size_t pos = 8;  // strlen("generic_")
    const size_t start = pos;
    while (pos < name.size() &&
           isdigit(static_cast<unsigned char>(name[pos]))) {
      ++pos;
    }
    if (pos == start || pos >= name.size() || name[pos] != '-') {
      return false;
    }
    rest = name.substr(pos + 1);
  } else {
    return false;
  }

  return IsIso8601UtcTimestamp(rest);
}

/**
 * Set a generic tag name of the form "generic-YYYY-MM-DDThh:mm:ss.sssZ"
 */
void RepositoryTag::SetGenericName() {
  const uint64_t nanoseconds = platform_realtime_ns();

  // Use strftime() to format timestamp to one-second resolution
  const time_t seconds = static_cast<time_t>(nanoseconds / 1000000000);
  struct tm timestamp;
  gmtime_r(&seconds, &timestamp);
  char seconds_buffer[32];
  strftime(seconds_buffer, sizeof(seconds_buffer), "generic-%Y-%m-%dT%H:%M:%S",
           &timestamp);

  // Append milliseconds
  const unsigned offset_milliseconds = ((nanoseconds / 1000000) % 1000);
  char name_buffer[48];
  snprintf(name_buffer, sizeof(name_buffer), "%s.%03dZ", seconds_buffer,
           offset_milliseconds);

  name_ = std::string(name_buffer);
}
