Prisoners = {}
Breakouts = {}

function CheckRequired(source, required)
    if not required or #required < 1 then return true end
    local success = true
    local missingItems = {}
    for i=1, #required do 
        local part = required[i]
        if (not part.type or part.type == "item") then
            local remaining = Inventory.GetItemCount(source, part.name) - (part.amount)
            if remaining < 0 then 
                success = false
                missingItems[#missingItems + 1] = {index = i, name = part.name, count = remaining * -1}
            end
        elseif (part.type == "cash") then
            local remaining = GetMoney(source) - part.amount
            if remaining < 0 then 
                success = false
                missingItems[#missingItems + 1] = {index = i, name = "cash", count = remaining * -1}
            end
        elseif (part.type == "weapon" and not Inventory.HasWeapon(source, part.name)) then
            success = false
            missingItems[#missingItems + 1] = {index = i, name = part.name, count = 1}
        end
    end
    return success, missingItems
end

function TakeRequired(source, required)
    if not required or #required < 1 then return true end
    for i=1, #required do 
        local part = required[i]
        if (not part.type or part.type == "item") then
            Inventory.RemoveItem(source, part.name, part.amount)
        elseif (part.type == "cash") then
            RemoveMoney(source, part.amount)
        elseif (part.type == "weapon" and not Inventory.HasWeapon(source, part.name)) then
            Inventory.RemoveWeapon(source, part.name, 1)
        end
    end
end

function GiveRewards(source, rewards)
    for i=1, #rewards do 
        local reward = rewards[i]
        if not reward.type or reward.type == "item" then
            Inventory.AddItem(source, reward.name, reward.amount, reward.createItem and reward.createItem(craftingData) or nil)
        elseif reward.type == "cash" then
            AddMoney(source, reward.amount)
        elseif reward.type == "weapon" then
            Inventory.AddWeapon(source, reward.name, reward.amount, reward.createItem and reward.createItem(craftingData) or nil)
        elseif Config.XPEnabled and reward.type == "xp" then 
            AddPlayerXP(source, reward.name, reward.amount)
        end
    end
end

function TakeInventory(source)
    local inventory = {}
    local data = Inventory.GetInventory(source)
    for i=1, #data do 
        local take = true
        for j=1, #Config.UnrevokedItems do 
            if data[i].name == Config.UnrevokedItems[j] then
                take = false
            end
        end
        if take then
            inventory[#inventory + 1] = data[i]
            Inventory.RemoveItem(source, data[i].name, data[i].count)
        end
    end
    return inventory
end

-- Define the JailPlayer function
function JailPlayer(source, time, index, noSave)
    if Prisoners[source] then return end
    local index = index or "default"
    local prison = Config.Prisons[index]
    if not time or not prison then return end
    local identifier = GetIdentifier(source)
    Prisoners[source] = {
        identifier = identifier,
        index = index,
        time = time,
        inventory = TakeInventory(source),
        sentence_date = os.time(),
    }
    SetPlayerMetadata(source, "injail", time)
    TriggerClientEvent("pickle_prisons:jailPlayer", source, Prisoners[source])
    if noSave then return end
    MySQL.Async.execute("DELETE FROM pickle_prisons WHERE identifier=@identifier;", {["@identifier"] = identifier})
    MySQL.Async.execute("INSERT INTO pickle_prisons (identifier, prison, time, inventory, sentence_date) VALUES (@identifier, @prison, @time, @inventory, @sentence_date);", {
        ["@identifier"] = Prisoners[source].identifier,
        ["@prison"] = Prisoners[source].index,
        ["@time"] = Prisoners[source].time,
        ["@inventory"] = json.encode(Prisoners[source].inventory),
        ["@sentence_date"] = Prisoners[source].sentence_date,
    })
end

-- Export the JailPlayer function
exports("JailPlayer", JailPlayer)

function UnjailPlayer(source, breakout)
    local data = Prisoners[source]
    if not data then return end
    local inventory = Prisoners[source].inventory
    Prisoners[source] = nil
    local identifier = GetIdentifier(source)
    MySQL.Async.execute("DELETE FROM pickle_prisons WHERE identifier=@identifier;", {["@identifier"] = identifier})
    SetPlayerMetadata(source, "injail", 0)
    StopActivity(source)
    if breakout then return end
    for i=1, #inventory do
        Inventory.AddItem(source, inventory[i].name, inventory[i].count, inventory[i].metadata)
    end
    TriggerClientEvent("pickle_prisons:unjailPlayer", source, data)
end

function UpdatePrisonTime(source, time)
    local identifier = GetIdentifier(source)
    MySQL.Async.execute("UPDATE pickle_prisons SET time=@time WHERE identifier=@identifier", {
        ["@identifier"] = identifier,
        ["@time"] = time,
    })
end

RegisterCallback("pickle_prisons:canBreakout", function(source, cb, index)
    if Breakouts[index] then return cb(false) end
    local required = Config.Breakout.required
    local success, missingItems = CheckRequired(source, required)
    if not success then 
        for i=1, #missingItems do 
            ShowNotification(source, _L("missing_item", missingItems[i].name, missingItems[i].count))
        end
        return cb(false)
    end
    cb(true)
end)

RegisterCallback("pickle_prisons:enterBreakoutPoint", function(source, cb, index, name)
    if name == "enter" and not Breakouts[index] then 
        cb(false)
    else
        cb(true)
    end
end)

RegisterNetEvent("pickle_prisons:startBreakout", function(index)
    local source = source
    if Breakouts[index] then return end
    local required = Config.Breakout.required
    local success, missingItems = CheckRequired(source, required)
    if not success then 
        for i=1, #missingItems do 
            ShowNotification(source, _L("missing_item", missingItems[i].name, missingItems[i].count))
        end
        return
    end
    TakeRequired(source, required)
    Breakouts[index] = {}
    TriggerClientEvent("pickle_prisons:startBreakout", -1, index)
    if Config.Breakout.alert then 
        StartSiren(index, Config.Breakout.time)
    end
    SetTimeout(1000 * Config.Breakout.time, function()
        if Config.Breakout.alert then 
            StopSiren(index, Config.Breakout.time)
        end
        Breakouts[index] = nil
        TriggerClientEvent("pickle_prisons:stopBreakout", -1, index)
    end)
end)

RegisterNetEvent("pickle_prisons:initializePlayer", function()
    local source = source
    local identifier = GetIdentifier(source)
    MySQL.Async.fetchAll("SELECT * FROM pickle_prisons WHERE identifier=@identifier;", {["@identifier"] = identifier}, function(results) 
        local result = results[1]
        if result then 
            local time = result.time
            if Config.ServeTimeOffline then
                time = (os.time() - result.sentence_date)
            end
            Prisoners[source] = {
                identifier = result.identifier,
                index = result.prison,
                time = time,
                inventory = json.decode(result.inventory),
                sentence_date = result.sentence_date,
            } 
            if time <= 0 then 
                return UnjailPlayer(source)
            end  
            SetPlayerMetadata(source, "injail", time)
            TriggerClientEvent("pickle_prisons:jailPlayer", source, Prisoners[source])
        end
    end)
    UpdateLootables(source)
    TriggerClientEvent("pickle_prisons:setupInventory", source, {items = Inventory.Items})
end)

RegisterNetEvent("pickle_prisons:breakout", function()
    local source = source
    UnjailPlayer(source, true)
    ShowNotification(source, _L("breakout_self"))
end)

RegisterNetEvent("pickle_prisons:jailPlayer", function(target, time, index)
    local source = source
    JailEvent(source, target, time, index)
end)

RegisterNetEvent("pickle_prisons:unjailPlayer", function(target)
    local source = source
    JailEvent(source, target)
end)

AddEventHandler("playerDropped", function()
    local source = source
    local identifier = GetIdentifier(source)
    if not Prisoners[source] then return end
    UpdatePrisonTime(source, Prisoners[source].time)
    Prisoners[source] = nil
end)

AddEventHandler('onResourceStop', function(resourceName)
    if (GetCurrentResourceName() ~= resourceName) then return end
    for source,v in pairs(Prisoners) do 
        UpdatePrisonTime(source, v.time)
    end
end)

function JailEvent(source, target, time, index)
    if not target or target < 1 then return end 
    if not GetPlayerName(target) then return end
    local prisoner = Prisoners[target]
    if prisoner then 
        local prison = Config.Prisons[prisoner.index]
        return ShowNotification(source, _L("in_prison", target, prisoner.time, prison.label))
    else
        local index = index or "default"
        local prison = Config.Prisons[index]
        if not prison then return end
        local permissions = prison.permissions or Config.Default.permissions 
        if not CheckPermission(source, permissions.jail) then 
            ShowNotification(source, _L("no_permission"))
        else
            ShowNotification(source, _L("in_prison", target, time, prison.label))
            JailPlayer(target, time, index)
        end
    end
end

function UnjailEvent(source, target)
    if not target or target < 1 then return end 
    local prisoner = Prisoners[target]
    if not prisoner then 
        return ShowNotification(source, _L("not_prison", target))
    else
        local index = prisoner.index
        local prison = Config.Prisons[index]
        local permissions = prison.permissions or Config.Default.permissions 
        if not CheckPermission(source, permissions.unjail) then 
            ShowNotification(source, _L("no_permission"))
        else
            UnjailPlayer(target)
        end
    end
end

function StartSiren(index, time)
    local prison = Config.Prisons[index]
    local permissions = prison.permissions or Config.Default.permissions
    local players = GetPlayers()
    for i=1, #players do 
        if CheckPermission(players[i], permissions.alert) then
            TriggerClientEvent("pickle_prisons:alert", players[i], index)
        end
    end
    TriggerClientEvent("pickle_prisons:startSiren", -1, index)
    if time then 
        SetTimeout(time * 1000, function()
            StopSiren(index)
        end)
    end
end

function StopSiren(index)
    local prison = Config.Prisons[index]
    local permissions = prison.permissions or Config.Default.permissions
    local players = GetPlayers()
    for i=1, #players do 
        if CheckPermission(players[i], permissions.alert) then
            TriggerClientEvent("pickle_prisons:alert", players[i], index, true)
        end
    end
    TriggerClientEvent("pickle_prisons:stopSiren", -1, index)
end

RegisterCommand("jailstatus", function(source, args, raw)
    local target = tonumber(args[1]) or source
    local prisoner = Prisoners[target]
    if not prisoner then 
        return ShowNotification(source, _L("not_prison", target))
    else
        local prison = Config.Prisons[prisoner.index]
        return ShowNotification(source, _L("in_prison", target, prisoner.time,  prison.label))
    end
end)

RegisterCommand("jail", function(source, args, raw)
    local target = tonumber(args[1])
    local time = tonumber(args[2])
    JailEvent(source, target, time, args[3])
end)

RegisterCommand("unjail", function(source, args, raw)
    local target = tonumber(args[1])
    UnjailEvent(source, target)
end)

RegisterCommand("startsiren", function(source, args, raw)
    local index = args[1]
    if not index or not Config.Prisons[index] then return end
    local prison = Config.Prisons[index]
    local permissions = prison.permissions or Config.Default.permissions
    if not CheckPermission(source, permissions.alert) then 
        ShowNotification(source, _L("no_permission"))
    else
        StartSiren(index)
    end
end)

RegisterCommand("stopsiren", function(source, args, raw)
    local index = args[1]
    if not index or not Config.Prisons[index] then return end
    local prison = Config.Prisons[index]
    local permissions = prison.permissions or Config.Default.permissions
    if not CheckPermission(source, permissions.alert) then 
        ShowNotification(source, _L("no_permission"))
    else
        StopSiren(index)
    end
end)

RegisterCommand("jailmenu", function(source, args, raw)
    local allowed = false
    for k,v in pairs(Config.Prisons) do
        local permissions = v.permissions or Config.Default.permissions
        if CheckPermission(source, permissions.alert) then 
            allowed = true
        end
    end
    if not allowed then 
        ShowNotification(source, _L("no_permission"))
    else
        TriggerClientEvent("pickle_prisons:jailDialog", source)
    end
end)

function PrisonTimer()
    for source,v in pairs(Prisoners) do 
        Prisoners[source].time = Prisoners[source].time - 1
        if Prisoners[source].time <= 0 then 
            UnjailPlayer(source)
        else 
            SetPlayerMetadata(source, "injail", Prisoners[source].time)
        end
    end
    SetTimeout(1000 * 60, PrisonTimer)
end

CreateThread(function()
    PrisonTimer()
end)
