-- Prevent running in monitor mode
if not TX_SERVER_MODE then return end
-- Prevent running if menu is disabled
if not TX_MENU_ENABLED then return end

if TX_LUACOMHOST == "invalid" or TX_LUACOMTOKEN == "invalid" then
  txPrint('^1API Host or Pipe Token ConVars not found. Do not start this resource if not using txAdmin.')
  return
end
if TX_LUACOMTOKEN == "removed" then
  txPrint('^1Please do not restart the monitor resource.')
  return
end


-- =============================================
--  This file is responsible for all the webpipe
--  handling and caching.
-- =============================================

local _pipeLastReject
local _pipeFastCache = {}

-- Check if we should use latent events
local useLatentEvents = GetConvarBool('sv_enableNetEventReassembly', true);
if not useLatentEvents then
  txPrint('^3WARNING: Latent events are disabled. If you have issues using the txAdmin in-game menu, please enable them by setting sv_enableNetEventReassembly to true in your server.cfg.')
end

-- So it's easy to enable-disable debug messages for webpipe
function debugWebPipe(...)
  -- debugPrint(...)
end

---Sends a response to the client
---@param src string
---@param callbackId number
---@param statusCode number
---@param path string
---@param body string
---@param headers table
---@param cached boolean|nil
local function sendResponse(src, callbackId, statusCode, path, body, headers, cached)
  local errorCode = tonumber(statusCode) >= 400
  local resultColor = errorCode and '^1' or '^2'
  local cachedStr = cached and " ^1(cached)^0" or ""
  debugWebPipe(("^3WebPipe[^5%d^0:^1%d^3]^0 %s<< %s ^4%s%s^0"):format(
    src, callbackId, resultColor, statusCode, path, cachedStr))
  if errorCode then
    debugWebPipe(("^3WebPipe[^5%d^0:^1%d^3]^0 %s<< Headers: %s^0"):format(src, callbackId, resultColor, json.encode(headers)))
  end
  if useLatentEvents then
    TriggerLatentClientEvent('txcl:webpipe:resp', src, 125000, callbackId, statusCode, body, headers)
  else
    TriggerClientEvent('txcl:webpipe:resp', src, callbackId, statusCode, body, headers)
  end
end

---Handles incoming webpipe request events
---@param callbackId number
---@param method string
---@param path string
---@param headers table
---@param body string
RegisterNetEvent('txsv:webpipe:req', function(callbackId, method, path, headers, body)
  local s = source
  local src = tostring(s)
  if type(callbackId) ~= 'number' or type(headers) ~= 'table' then
    return
  end
  if type(method) ~= 'string' or type(path) ~= 'string' or type(body) ~= 'string' then
    return
  end

  -- Reject large paths as we use regex
  if #path > 500 then
    return sendResponse(s, callbackId, 400, path:sub(1, 300), "{}", {})
  end

  -- Treat path slashes
  local url = "http://" .. (TX_LUACOMHOST .. '/' .. path):gsub("//+", "/")

  -- Reject requests from un-authed players
  if not TX_ADMINS[src] then
    if _pipeLastReject ~= nil then
      if (GetGameTimer() - _pipeLastReject) < 1250 then
        _pipeLastReject = GetGameTimer()
        return
      end
    end
    debugWebPipe(string.format(
        "^3WebPipe[^5%d^0:^1%d^3]^0 ^1rejected request from ^3%s^1 for ^5%s^0", s, callbackId, s, path))
    TriggerClientEvent('txcl:webpipe:resp', s, callbackId, 403, "{}", {})
    return
  end

  -- Return fast cache
  if _pipeFastCache[path] ~= nil then
    local cachedData = _pipeFastCache[path]
    sendResponse(s, callbackId, 200, path, cachedData.data, cachedData.headers, true)
    return
  end

  -- Adding admin identifiers for auth middleware to deal with
  headers['X-TxAdmin-Token'] = TX_LUACOMTOKEN
  headers['X-TxAdmin-Identifiers'] = table.concat(GetPlayerIdentifiers(s), ',')

  debugWebPipe(("^3WebPipe[^5%d^0:^1%d^3]^0 ^4>>^0 ^6%s^0"):format(s, callbackId, url))
  debugWebPipe(("^3WebPipe[^5%d^0:^1%d^3]^0 ^4>>^0 ^6Headers: %s^0"):format(s, callbackId, json.encode(headers)))

  PerformHttpRequest(url, function(httpCode, data, resultHeaders)
    -- fixing body for error pages (eg 404)
    -- this is likely because of how json.encode() interprets null and an empty table
    data = data or ''
    resultHeaders['x-badcast-fix'] = 'https://youtu.be/LDU_Txk06tM' -- fixed in artifact v3996
    --https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
    resultHeaders['Content-Security-Policy'] = 'frame-ancestors https://monitor/ https://cfx-nui-monitor/ nui://game/'

    -- fixing redirects
    if resultHeaders.Location then
      if resultHeaders.Location:sub(1, 1) == '/' then
        resultHeaders.Location = '/WebPipe' .. resultHeaders.Location
      end
    end

    -- fixing cookies
    if resultHeaders['Set-Cookie'] then
      local cookieHeader = resultHeaders['Set-Cookie']
      local cookies = type(cookieHeader) == 'table' and cookieHeader or { cookieHeader }

      for k in pairs(cookies) do
        cookies[k] = cookies[k] .. '; SameSite=None; Secure'
      end

      resultHeaders['Set-Cookie'] = cookies
    end

    -- cache response if it is a static file
    local sub = string.sub
    if 
      httpCode == 200 and 
      (
        sub(path, 1, 5) == '/css/' or 
        sub(path, 1, 4) == '/js/' or 
        sub(path, 1, 5) == '/img/' or 
        sub(path, 1, 7) == '/fonts/'
      ) 
    then
      -- remove query params from path, so people can't consume memory by spamming cache-busters
      for safePath in path:gmatch("([^?]+)") do
        local slimHeaders = {}
        for k, v in pairs(resultHeaders) do
          if k ~= 'Set-Cookie' then
            slimHeaders[k] = v
          end
        end
        _pipeFastCache[safePath] = { data = data, headers = slimHeaders }
        debugWebPipe(("^3WebPipe[^5%d^0:^1%d^3]^0 ^5cached ^4%s^0"):format(s, callbackId, safePath))
        break
      end
    end

    sendResponse(s, callbackId, httpCode, path, data, resultHeaders)
  end, method, body, headers, {followLocation = false})
end)
