local DR = select(2, ...)
local constants = DR.Modules["Core.Constants"]

local Protocol = {
  messageCounter = 0,
}

local function nowTs()
  if GetServerTime then
    return GetServerTime()
  end
  return time()
end

-- Midnight (12.x) secret values cannot be serialized, compared, or concatenated
-- on addon execution paths. Strip any secret that sneaks into an outgoing
-- payload so a tainted value can never hard-error the comms layer.
local isSecretValue = rawget(_G, "issecretvalue")
local isSecretTable = rawget(_G, "issecrettable")

local function sanitizeValue(value, depth)
  if isSecretValue and isSecretValue(value) then
    return nil
  end

  if type(value) == "table" then
    if isSecretTable and isSecretTable(value) then
      return nil
    end
    if depth > 10 then
      return nil
    end
    local out = {}
    for k, v in pairs(value) do
      local key = sanitizeValue(k, depth + 1)
      local val = sanitizeValue(v, depth + 1)
      if key ~= nil and val ~= nil then
        out[key] = val
      end
    end
    return out
  end

  return value
end

function Protocol:SanitizePayload(payload)
  return sanitizeValue(payload or {}, 0) or {}
end

function Protocol:OnInitialize()
  self.messageCounter = 0
end

function Protocol:NextMessageId()
  self.messageCounter = self.messageCounter + 1
  local guid = UnitGUID("player") or "noguid"
  return string.format("%s-%d-%d", guid, nowTs(), self.messageCounter)
end

function Protocol:BuildEnvelope(args)
  local envelope = {
    protocolVersion = constants.VERSION.PROTOCOL,
    messageType = args.messageType,
    sessionId = args.sessionId,
    itemNonce = args.itemNonce,
    revision = args.revision or 0,
    sender = args.sender or UnitGUID("player") or "unknown",
    timestamp = nowTs(),
    messageId = args.messageId or self:NextMessageId(),
    payload = self:SanitizePayload(args.payload),
  }

  return envelope
end

function Protocol:BuildAckEnvelope(ackMessageId)
  return self:BuildEnvelope({
    messageType = constants.COMMS.MESSAGE.ACK,
    payload = {
      ackMessageId = ackMessageId,
    },
  })
end

function Protocol:IsReliable(messageType)
  return constants.COMMS.RELIABLE[messageType] == true
end

function Protocol:IsValidEnvelope(envelope)
  if type(envelope) ~= "table" then
    return false
  end

  if not envelope.messageType or not envelope.messageId then
    return false
  end

  if type(envelope.payload) ~= "table" then
    return false
  end

  return true
end

DR:RegisterModule("Services.Protocol", Protocol)
