--[[
    https://github.com/overextended/ox_lib

    This file is licensed under LGPL-3.0 or higher <https://www.gnu.org/licenses/lgpl-3.0.en.html>

    Copyright © 2025 Linden <https://github.com/thelindat>
]]

local service = GetConvar('ox:logger', 'datadog')
local buffer
local bufferSize = 0

local function removeColorCodes(str)
    -- replace ^[0-9] with nothing
    str = string.gsub(str, "%^%d", "")

    -- replace ^#[0-9A-F]{3,6} with nothing
    str = string.gsub(str, "%^#[%dA-Fa-f]+", "")

    -- replace ~[a-z]~ with nothing
    str = string.gsub(str, "~[%a]~", "")

    return str
end

local hostname = removeColorCodes(GetConvar('ox:logger:hostname', GetConvar('sv_projectName', 'fxserver')))

local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

local function base64encode(data)
    return ((data:gsub(".", function(x)
        local r, b = "", x:byte()
        for i = 8, 1, -1 do
            r = r .. (b % 2 ^ i - b % 2 ^ (i - 1) > 0 and "1" or "0")
        end
        return r;
    end) .. "0000"):gsub("%d%d%d?%d?%d?%d?", function(x)
        if (#x < 6) then
            return ""
        end
        local c = 0
        for i = 1, 6 do
            c = c + (x:sub(i, i) == "1" and 2 ^ (6 - i) or 0)
        end
        return b:sub(c + 1, c + 1)
    end) .. ({"", "==", "="})[#data % 3 + 1])
end

local function getAuthorizationHeader(user, password)
    return "Basic " .. base64encode(user .. ":" .. password)
end


local function badResponse(endpoint, status, response)
    warn(('unable to submit logs to %s (status: %s)\n%s'):format(endpoint, status, json.encode(response, { indent = true })))
end

local playerData = {}

AddEventHandler('playerDropped', function()
    playerData[source] = nil
end)

local function formatTags(source, tags)
    if type(source) == 'number' and source > 0 then
        local data = playerData[source]

        if not data then
            local _data = {
                ('username:%s'):format(GetPlayerName(source))
            }

            local num = 1

            ---@cast source string
            for i = 0, GetNumPlayerIdentifiers(source) - 1 do
                local identifier = GetPlayerIdentifier(source, i)

                if not identifier:find('ip') then
                    num += 1
                    _data[num] = identifier
                end
            end

            data = table.concat(_data, ',')
            playerData[source] = data
        end

        tags = tags and ('%s,%s'):format(tags, data) or data
    end

    return tags
end

if service == 'fivemanage' then
    local key = GetConvar('fivemanage:key', '')

    if key ~= '' then
        local endpoint = 'https://api.fivemanage.com/api/logs/batch'

        local headers = {
            ['Content-Type'] = 'application/json',
            ['Authorization'] = key,
            ['User-Agent'] = 'ox_lib'
        }

        function lib.logger(source, event, message, ...)
            if not buffer then
                buffer = {}

                SetTimeout(500, function()
                    PerformHttpRequest(endpoint, function(status, _, _, response)
                        if status ~= 200 then 
                            if type(response) == 'string' then
                                response = json.decode(response) or response
                                badResponse(endpoint, status, response)
                            end
                        end
                    end, 'POST', json.encode(buffer), headers)

                    buffer = nil
                    bufferSize = 0
                end)
            end

            bufferSize += 1
            buffer[bufferSize] = {
                level = "info",
                message = message,
                resource = cache.resource,
                metadata = {
                    hostname = hostname,
                    service = event,
                    source = source,
                    tags = formatTags(source, ... and string.strjoin(',', string.tostringall(...)) or nil),
                }
            }
        end
    end
end

if service == 'datadog' then
    local key = GetConvar('datadog:key', ''):gsub("[\'\"]", '')

    if key ~= '' then
        local endpoint = ('https://http-intake.logs.%s/api/v2/logs'):format(GetConvar('datadog:site', 'datadoghq.com'))

        local headers = {
            ['Content-Type'] = 'application/json',
            ['DD-API-KEY'] = key,
        }

        function lib.logger(source, event, message, ...)
            if not buffer then
                buffer = {}

                SetTimeout(500, function()
                    PerformHttpRequest(endpoint, function(status, _, _, response)
                        if status ~= 202 then
                            if type(response) == 'string' then
                                response = json.decode(response:sub(10)) or response
                                badResponse(endpoint, status, type(response) == 'table' and response.errors[1] or response)
                            end
                        end
                    end, 'POST', json.encode(buffer), headers)

                    buffer = nil
                    bufferSize = 0
                end)
            end

            bufferSize += 1
            buffer[bufferSize] = {
                hostname = hostname,
                service = event,
                message = message,
                resource = cache.resource,
                ddsource = tostring(source),
                ddtags = formatTags(source, ... and string.strjoin(',', string.tostringall(...)) or nil),
            }
        end
    end
end

if service == 'loki' then
    local lokiUser = GetConvar('loki:user', '')
    local lokiPassword = GetConvar('loki:password', GetConvar('loki:key', ''))
    local lokiEndpoint = GetConvar('loki:endpoint', '')
    local lokiTenant = GetConvar('loki:tenant', '')
    local startingPattern = '^http[s]?://'
    local headers = {
        ['Content-Type'] = 'application/json'
    }

    if lokiUser ~= '' then
        headers['Authorization'] = getAuthorizationHeader(lokiUser, lokiPassword)
    end

    if lokiTenant ~= '' then
        headers['X-Scope-OrgID'] = lokiTenant
    end

    if not lokiEndpoint:find(startingPattern) then
        lokiEndpoint = 'https://' .. lokiEndpoint
    end

    local endpoint = ('%s/loki/api/v1/push'):format(lokiEndpoint)

    -- Converts a string of comma seperated kvp string to a table of kvps
    -- example `discord:blahblah,fivem:blahblah,license:blahblah` -> `{discord="blahblah",fivem="blahblah",license="blahblah"}`
    local function convertDDTagsToKVP(tags)
        if not tags or type(tags) ~= 'string' then
            return {}
        end
        local tempTable = { string.strsplit(',', tags) } -- outputs a number index table wth k:v strings as values
        local bTable = table.create(0, #tempTable) -- buffer table

        -- Loop through table and grab only values
        for _, v in pairs(tempTable) do
            local key, value = string.strsplit(':', v) -- splits string on ':' character
            bTable[key] = value
        end

        return bTable -- Return the new table of kvps
    end

    function lib.logger(source, event, message, ...)
        if not buffer then
            buffer = {}

            SetTimeout(500, function()
                -- Strip string keys from buffer
                local tempBuffer = {}
                for _,v in pairs(buffer) do
                    tempBuffer[#tempBuffer+1] = v
                end

                local postBody = json.encode({streams = tempBuffer})
                PerformHttpRequest(endpoint, function(status, _, _, _)
                    if status ~= 204 then
                        badResponse(endpoint, status, ("%s"):format(status, postBody))
                    end
                end, 'POST', postBody, headers)

                buffer = nil
            end)
        end

        -- Generates a nanosecond unix timestamp
        ---@diagnostic disable-next-line: param-type-mismatch
        local timestamp = ('%s000000000'):format(os.time(os.date('*t')))

        -- Initializes values table with the message
        local values = {message = message}

        -- Format the args into strings
        local tags = formatTags(source, ... and string.strjoin(',', string.tostringall(...)) or nil)
        local tagsTable = convertDDTagsToKVP(tags)

        -- Concatenates tags kvp table to the values table
        for k,v in pairs(tagsTable) do
            values[k] = v -- Store the tags in the values table ready for logging
        end

        -- initialise stream payload
        local payload = {
            stream = {
                server = hostname,
                resource = cache.resource,
                event = event
            },
            values = {
                {
                    timestamp,
                    json.encode(values)
                }
            }
        }

        -- Safety check incase it throws index issue
        if not buffer then
            buffer = {}
        end

        -- Checks if the event exists in the buffer and adds to the values if found
        -- else initialises the stream
        if not buffer[event] then
            buffer[event] = payload
        else
            local lastIndex = #buffer[event].values
            lastIndex += 1

            buffer[event].values[lastIndex] = {
                timestamp,
                json.encode(values)
            }
        end
	end
end

return lib.logger
