local QBCore = exports['qb-core']:GetCoreObject()
local playerBuffs = {}
local next = next

--- Adds a buff to player
--- @param citizenID string - Player identifier
--- @param buffName string - Name of the buff
--- @param time number | nil - Optional time to add or how long you want buff to be
--- @return bool
local function AddBuff(sourceID, citizenID, buffName, time)
    local buffData = Config.Buffs[buffName]
    -- Check if we were given a correct buff name
    if buffData == nil then
        return false
    end

    -- If the player had no buffs at all then add them to the table
    if not playerBuffs[citizenID] then
        playerBuffs[citizenID] = {}
    end

    local maxTime = buffData.maxTime

    -- If the player didnt already have the buff requested set it to time or maxTime
    if not playerBuffs[citizenID][buffName] then
        local buffTime = maxTime
        if time then
            buffTime = time
        end
        
        playerBuffs[citizenID][buffName] = buffTime
        
        -- Since the player didnt already have this buff tell the front end to show it
        if buffData.type == 'buff' then
            -- Call client event to send nui to front end to start showing buff
            TriggerClientEvent('hud:client:BuffEffect', sourceID, {
                buffName = buffName,
                display = true,
                iconName = buffData.iconName,
                iconColor = buffData.iconColor,
                progressColor = buffData.progressColor,
                progressValue = (buffTime * 100) / buffData.maxTime,
            })
        else
            -- Call client event to send nui to front end to start showing enhancement
            TriggerClientEvent('hud:client:EnhancementEffect', sourceID, {
                display = true,    
                enhancementName = buffName,
                iconColor = buffData.iconColor
            })
        end

    else
        -- Since the player already had a buff increase the buff time, but not higher than max buff time
        local newTime = playerBuffs[citizenID][buffName] + time
        
        if newTime > maxTime then
            newTime = maxTime
        end
        
        playerBuffs[citizenID][buffName] = newTime
    end

    return true
end exports('AddBuff', AddBuff)

--- Removes a buff from provided player
--- @param citizenID string - Player identifier
--- @param buffName string - Name of the buff
--- @return bool - Success of removing the player buff
local function Removebuff(citizenID, buffName)
    local buffData = Config.Buffs[buffName]
    if playerBuffs[citizenID] and playerBuffs[citizenID][buffName] then
        
        playerBuffs[citizenID][buffName] = nil

        local player = QBCore.Functions.GetPlayerByCitizenId(citizenID)
        local sourceID = nil

        if player then
            sourceID = player.PlayerData.source
        end

        -- Check if player is online
        if sourceID then
            -- Send a nui call to front end to stop showing icon
            if buffData.type == 'buff' then
                -- Call client event to send nui to front end to stop showing buff
                TriggerClientEvent('hud:client:BuffEffect', sourceID, {
                    display = false,
                    buffName = buffName,
                })
            else
                -- Call client event to send nui to front end to stop showing enhancement
                TriggerClientEvent('hud:client:EnhancementEffect', sourceID, {
                    display = false,    
                    enhancementName = buffName,
                })
            end
        end

        -- Check to see if that was the player's last buff
        -- If so, then remove the player from the table to ensure we dont loop them
        if next(playerBuffs[citizenID]) == nil then
            playerBuffs[citizenID] = nil
        end

        return true
    end

    return false
end exports('RemoveBuff', RemoveBuff)

--- Method to fetch if player has buff with name and is not nil
--- @param citizenID string - Player identifier
--- @param buffName string - Name of the buff
--- @return bool
local function HasBuff(citizenID, buffName)
    if playerBuffs[citizenID] then
        return playerBuffs[citizenID][buffName] ~= nil
    end

    return false
end exports('HasBuff', HasBuff)

QBCore.Functions.CreateCallback('buffs:server:fetchBuffs', function(source, cb)
    local player = QBCore.Functions.GetPlayer(source)
    local citizenID = player.PlayerData.citizenid
    cb(playerBuffs[citizenID])
end)

QBCore.Functions.CreateCallback('buffs:server:addBuff', function(source, cb, buffName, time)
    local player = QBCore.Functions.GetPlayer(source)
    local citizenID = player.PlayerData.citizenid
    cb(AddBuff(source, citizenID, buffName, time))
end)

CreateThread(function()
    local function DecrementBuff(sourceID, citizenID, buffName, currentTime)
        local buffData = Config.Buffs[buffName]
        local updatedTime = currentTime - Config.TickTime

        -- Buff ran out of time we need to remove it from the player
        if updatedTime <= 0 then
            -- Only need to update buffs since they show progress on client
            if buffData.type == 'buff' then
                -- Check if player is online
                if sourceID then
                    -- Call client event to send nui to front end, progress at 0
                    TriggerClientEvent('hud:client:BuffEffect', sourceID, {
                        buffName = buffName,
                        progressValue = 0,
                    })
                end
            end
            Removebuff(citizenID, buffName)
        else
            playerBuffs[citizenID][buffName] = updatedTime
            -- Only need to update buffs since they show progress on client
            if buffData.type == 'buff' then
                -- Check if player is online
                if sourceID then
                    -- Call client event to send nui to front end
                    TriggerClientEvent('hud:client:BuffEffect', sourceID, {
                        buffName = buffName,
                        -- Progress value needs to be from 0 - 100
                        progressValue = (updatedTime * 100) / buffData.maxTime,
                    })
                end
            end
        end
    end

    -- Not proud but need to loop through all timers but decrement it
    -- Loop is good as long as we only loop players that have buffs
    -- We ensure that when removing any buff, we check to see if it was the player's last buff
    -- Then we remove that player from our table to ensure we dont loop them
    while true do
        for citizenID, buffTable in pairs(playerBuffs) do
            local player = QBCore.Functions.GetPlayerByCitizenId(citizenID)
            local sourceID = nil
            
            if player then
                sourceID = player.PlayerData.source
            end
            
            for buffName, currentTime in pairs(buffTable) do
                DecrementBuff(sourceID, citizenID, buffName, currentTime)
            end
        end

        Wait(Config.TickTime)
    end
end)