Bridge.Database.AfterMigration("sky_phone", function()

SkyPhoneSim = {}

local pending_insertions = {}
local operation_locks = {}
local sim_types = {}

local function validate_number_configuration()
    local valid, validation_error = SkyPhoneSimNumber.ValidateConfiguration(
        Config.Sim.NumberLength,
        Config.Sim.NumberPrefix
    )
    if not valid then
        error(("[sky_phone] Invalid SIM number configuration: %s."):format(validation_error))
    end
end

local function refresh_sim_types()
    sim_types = {
        [Config.Sim.RegisteredItem] = "registered",
        [Config.Sim.AnonymousItem] = "anonymous",
    }
end

validate_number_configuration()
refresh_sim_types()

local function affected_rows(result)
    if type(result) == "number" then
        return result
    end
    return type(result) == "table" and tonumber(result.affectedRows) or 0
end

local function uuid()
    local rows = Bridge.Database.Query("SELECT UUID() AS `id`", {})
    if not rows[1] or type(rows[1].id) ~= "string" then
        error("[sky_phone] Database did not generate a SIM UUID.")
    end
    return rows[1].id
end

local function reserve_sim(sim_type, is_virtual)
    local sim_id
    local number = SkyPhoneSimNumber.Reserve(uuid, function(candidate)
        if SkyPhoneCompanies.IsServiceNumber(candidate) then
            return false
        end
        sim_id = uuid()
        local result = Bridge.Database.Query([[
            INSERT IGNORE INTO `sky_phone_sims` (`id`, `phone_number`, `sim_type`, `is_virtual`)
            VALUES (?, ?, ?, ?)
        ]], { sim_id, candidate, sim_type, is_virtual and 1 or 0 })
        return affected_rows(result) == 1
    end, Config.Sim.NumberLength, Config.Sim.NumberPrefix)
    if not number then
        error("[sky_phone] Could not reserve a unique SIM number after 20 attempts.")
    end
    return { id = sim_id, phone_number = number, sim_type = sim_type }
end

local function load_sim(sim_id)
    local rows = Bridge.Database.Query("SELECT * FROM `sky_phone_sims` WHERE `id` = ? LIMIT 1", { sim_id })
    return rows[1]
end

local function sim_metadata(sim)
    local metadata = {
        sim_metadata_version = 1,
        sim_id = sim.id,
        phone_number = sim.phone_number,
        formatted_number = SkyPhoneSimNumber.Format(sim.phone_number, Config.Sim.NumberGroups, Config.Sim.NumberLength, Config.Sim.NumberPrefix),
        sim_type = sim.sim_type,
    }
    if sim.sim_type == "registered" and sim.owner_identifier then
        metadata.firstname = sim.owner_firstname
        metadata.lastname = sim.owner_lastname
        metadata.birthdate = sim.owner_birthdate
        metadata.registered_at = sim.registered_at
    end
    return metadata
end

local function set_phone_sim_metadata(source, phone_slot, sim)
    if Config.Phone.Unique == false then
        return true
    end
    if not phone_slot then
        return false
    end

    local metadata = phone_slot.metadata or {}
    metadata.sim_id = sim and sim.id or nil
    metadata.phone_number = sim and sim.phone_number or nil
    metadata.formatted_number = sim and SkyPhoneSimNumber.Format(
        sim.phone_number,
        Config.Sim.NumberGroups,
        Config.Sim.NumberLength,
        Config.Sim.NumberPrefix
    ) or nil
    return Bridge.Inventory.SetSlotMetadata(source, phone_slot.slot, metadata)
end

local function prepare_device(source, phone_slot, imei)
    local device = SkyPhone.LoadDevice(imei)
    if not device then
        return false, "request_failed"
    end

    local sim = device.sim_id and load_sim(device.sim_id) or nil
    if Config.Sim.Enabled ~= false then
        if sim and tonumber(sim.is_virtual) == 1 then
            local result = Bridge.Database.Query([[
                UPDATE `sky_phone_devices`
                SET `sim_id` = NULL
                WHERE `imei` = ? AND `sim_id` = ?
            ]], { imei, sim.id })
            if affected_rows(result) ~= 1 then
                return false, "request_failed"
            end
            sim = nil
        end

        if not set_phone_sim_metadata(source, phone_slot, sim) then
            return false, "metadata_unsupported"
        end
        return true
    end

    if sim then
        if not set_phone_sim_metadata(source, phone_slot, sim) then
            return false, "metadata_unsupported"
        end
        return true
    end

    local automatic_sim = reserve_sim("anonymous", true)
    local result = Bridge.Database.Query([[
        UPDATE `sky_phone_devices`
        SET `sim_id` = ?
        WHERE `imei` = ? AND `sim_id` IS NULL
    ]], { automatic_sim.id, imei })
    if affected_rows(result) ~= 1 then
        Bridge.Database.Query("DELETE FROM `sky_phone_sims` WHERE `id` = ?", { automatic_sim.id })
        device = SkyPhone.LoadDevice(imei)
        sim = device and device.sim_id and load_sim(device.sim_id) or nil
        if not sim then
            return false, "request_failed"
        end
        if not set_phone_sim_metadata(source, phone_slot, sim) then
            return false, "metadata_unsupported"
        end
        return true
    end

    if not set_phone_sim_metadata(source, phone_slot, automatic_sim) then
        Bridge.Database.Query(
            "UPDATE `sky_phone_devices` SET `sim_id` = NULL WHERE `imei` = ? AND `sim_id` = ?",
            { imei, automatic_sim.id }
        )
        Bridge.Database.Query("DELETE FROM `sky_phone_sims` WHERE `id` = ?", { automatic_sim.id })
        return false, "metadata_unsupported"
    end
    TriggerEvent("sky_phone:server:phoneNumberGenerated", source, automatic_sim.phone_number)
    return true
end

SkyPhoneSim.PrepareDevice = prepare_device

function SkyPhoneSim.ChangeNumber(source, imei, sim_id, value)
    local number = SkyPhoneSimNumber.Normalize(value, Config.Sim.NumberLength, Config.Sim.NumberPrefix)
    if not number or SkyPhoneCompanies.IsServiceNumber(number) then
        return false, "invalid_phone_number"
    end

    local sim = load_sim(sim_id)
    if not sim then
        return false, "no_sim"
    end
    if sim.phone_number == number then
        return false, "phone_number_unchanged"
    end

    local existing = Bridge.Database.Query(
        "SELECT `id` FROM `sky_phone_sims` WHERE `phone_number` = ? AND `id` <> ? LIMIT 1",
        { number, sim_id }
    )
    if existing[1] then
        return false, "phone_number_taken"
    end

    local phone_slot
    if Config.Phone.Unique ~= false then
        for _, slot in ipairs(Bridge.Inventory.GetSlotsWithItem(source, Config.Phone.Item)) do
            if slot.metadata and slot.metadata.imei == imei then
                phone_slot = slot
                break
            end
        end
    end

    local previous_number = sim.phone_number
    sim.phone_number = number
    if phone_slot and not set_phone_sim_metadata(source, phone_slot, sim) then
        return false, "metadata_unsupported"
    end

    local result = Bridge.Database.Query([[
        UPDATE IGNORE `sky_phone_sims`
        SET `phone_number` = ?
        WHERE `id` = ? AND `phone_number` = ?
    ]], { number, sim_id, previous_number })
    if affected_rows(result) ~= 1 then
        if phone_slot then
            sim.phone_number = previous_number
            if not set_phone_sim_metadata(source, phone_slot, sim) then
                error("[sky_phone] Could not restore SIM metadata after a failed admin number change.")
            end
        end
        return false, "phone_number_taken"
    end

    return true, number
end

local function resolve_used_sim(source, used_item, item_name)
    local slot_id = used_item and (used_item.slot or used_item.id)
    local slot = slot_id and Bridge.Inventory.GetSlot(source, slot_id) or nil
    if slot and slot.name == item_name then
        return slot
    end
    local slots = Bridge.Inventory.GetSlotsWithItem(source, item_name)
    if #slots == 1 then
        return slots[1]
    end
    Bridge.Debug("warn", "[sky_phone] Could not resolve exact SIM slot for source %s.", tostring(source))
    return nil
end

local function ensure_sim(source, slot, sim_type)
    if (tonumber(slot.amount or slot.count) or 0) ~= 1 then
        return nil, "sim_stacked"
    end
    local metadata = slot.metadata or {}
    local sim = metadata.sim_id and load_sim(metadata.sim_id) or nil
    if metadata.sim_id and (
        not sim
        or tonumber(sim.is_virtual) == 1
        or sim.sim_type ~= sim_type
        or sim.phone_number ~= metadata.phone_number
    ) then
        return nil, "invalid_sim"
    end
    if sim and SkyPhoneCompanies.IsServiceNumber(sim.phone_number) then
        Bridge.Debug(
            "error",
            "[sky_phone] SIM %s uses reserved company service number %s.",
            tostring(sim.id),
            tostring(sim.phone_number)
        )
        return nil, "invalid_sim"
    end
    local generated = false
    if not sim then
        sim = reserve_sim(sim_type)
        if not Bridge.Inventory.SetSlotMetadata(source, slot.slot, sim_metadata(sim)) then
            Bridge.Database.Query("DELETE FROM `sky_phone_sims` WHERE `id` = ?", { sim.id })
            return nil, "metadata_unsupported"
        end
        generated = true
    end
    if generated then
        TriggerEvent("sky_phone:server:phoneNumberGenerated", source, sim.phone_number)
    end
    return sim
end

local function list_phone_choices(source)
    local choices = {}
    local seen = {}
    local choice_error
    for _, slot in ipairs(Bridge.Inventory.GetSlotsWithItem(source, Config.Phone.Item)) do
        local imei, error_code = SkyPhone.EnsureDevice(source, slot)
        if imei and not seen[imei] then
            local prepared, prepare_error = prepare_device(source, slot, imei)
            if prepared then
                local device = SkyPhone.LoadDevice(imei)
                choices[#choices + 1] = {
                    imei = imei,
                    name = device.device_name,
                    occupied = device.sim_id ~= nil,
                    number = device.phone_number,
                }
                seen[imei] = true
            else
                Bridge.Debug(
                    "error",
                    "[sky_phone] Could not prepare SIM target %s for source %s: %s.",
                    imei,
                    tostring(source),
                    tostring(prepare_error)
                )
                choice_error = choice_error or prepare_error
            end
        elseif not imei then
            choice_error = choice_error or error_code
        end
    end
    return choices, choice_error
end

local function rollback_phone_metadata(source, phone_slot, old_sim)
    set_phone_sim_metadata(source, phone_slot, old_sim)
end

local function insert_sim(source, phone_imei, confirmed)
    if Config.Sim.Enabled == false then
        return { success = false, error = "disabled" }
    end
    if operation_locks[source] then
        return { success = false, error = "operation_in_progress" }
    end
    local pending = pending_insertions[source]
    if not pending or GetGameTimer() - pending.created_at > 60000 then
        pending_insertions[source] = nil
        return { success = false, error = "sim_request_expired" }
    end
    if SkyPhoneCompanies.IsServiceNumber(pending.sim.phone_number) then
        pending_insertions[source] = nil
        Bridge.Debug(
            "error",
            "[sky_phone] Refused to insert SIM %s with reserved company service number %s.",
            tostring(pending.sim.id),
            tostring(pending.sim.phone_number)
        )
        return { success = false, error = "invalid_sim" }
    end
    local sim_slot = Bridge.Inventory.GetSlot(source, pending.slot)
    if not sim_slot or sim_slot.name ~= pending.item_name or not sim_slot.metadata
        or sim_slot.metadata.sim_id ~= pending.sim.id then
        return { success = false, error = "sim_not_owned" }
    end
    local phone_matches = SkyPhone.FindDeviceSlots(source, phone_imei)
    if not phone_matches[1] then
        return { success = false, error = "phone_not_owned" }
    end
    local phone_slot = phone_matches[1]
    local prepared, prepare_error = prepare_device(source, phone_slot, phone_imei)
    if not prepared then
        return { success = false, error = prepare_error }
    end
    local device = SkyPhone.LoadDevice(phone_imei)
    local old_sim = device.sim_id and load_sim(device.sim_id) or nil
    if old_sim and not confirmed then
        return { success = false, error = "confirmation_required", data = { requiresConfirmation = true } }
    end

    operation_locks[source] = true
    if not set_phone_sim_metadata(source, phone_slot, pending.sim) then
        operation_locks[source] = nil
        return { success = false, error = "metadata_unsupported" }
    end
    if Bridge.Inventory.RemoveItem(source, pending.item_name, 1, pending.slot) ~= 1 then
        rollback_phone_metadata(source, phone_slot, old_sim)
        operation_locks[source] = nil
        return { success = false, error = "sim_not_owned" }
    end
    local old_item_name = old_sim and (old_sim.sim_type == "registered" and Config.Sim.RegisteredItem or Config.Sim.AnonymousItem) or nil
    if old_sim and not Bridge.Inventory.AddItem(source, old_item_name, 1, pending.slot, sim_metadata(old_sim)) then
        Bridge.Inventory.AddItem(source, pending.item_name, 1, pending.slot, sim_metadata(pending.sim))
        rollback_phone_metadata(source, phone_slot, old_sim)
        operation_locks[source] = nil
        return { success = false, error = "inventory_full" }
    end

    local transaction = {
        {
            query = "UPDATE `sky_phone_devices` SET `sim_id` = ? WHERE `imei` = ?",
            params = { pending.sim.id, phone_imei },
        },
    }
    if pending.sim.sim_type == "registered" and not pending.sim.owner_identifier then
        transaction[#transaction + 1] = {
            query = [[
                UPDATE `sky_phone_sims`
                SET `owner_identifier` = ?, `owner_firstname` = ?, `owner_lastname` = ?,
                    `owner_birthdate` = ?, `registered_at` = CURRENT_TIMESTAMP
                WHERE `id` = ? AND `owner_identifier` IS NULL
            ]],
            params = {
                Bridge.Framework.GetIdentifier(source),
                Bridge.Framework.GetFirstname(source),
                Bridge.Framework.GetLastname(source),
                Bridge.Framework.GetBirthdate(source),
                pending.sim.id,
            },
        }
    end
    if not Bridge.Database.Transaction(transaction) then
        if old_sim then
            Bridge.Inventory.RemoveItem(source, old_sim.sim_type == "registered" and Config.Sim.RegisteredItem or Config.Sim.AnonymousItem, 1, pending.slot)
        end
        Bridge.Inventory.AddItem(source, pending.item_name, 1, pending.slot, sim_metadata(pending.sim))
        rollback_phone_metadata(source, phone_slot, old_sim)
        operation_locks[source] = nil
        return { success = false, error = "request_failed" }
    end

    pending_insertions[source] = nil
    operation_locks[source] = nil
    if old_sim then
        SkyPhoneCompanies.ClearCallAvailability(source)
        SkyPhoneCalls.EndForSim(old_sim.id, "sim_removed")
    end
    SkyPhone.RefreshDevice(phone_imei)
    TriggerClientEvent("sky_phone:sim:picker-close", source)
    return { success = true }
end

local function use_sim(source, used_item)
    if Config.Sim.Enabled == false then
        return false
    end
    local item_name = used_item and used_item.name
    local sim_type = item_name and sim_types[item_name]
    if not sim_type then
        Bridge.Debug("warn", "[sky_phone] Usable SIM callback received an invalid item for source %s.", tostring(source))
        return false
    end
    if operation_locks[source] then
        TriggerClientEvent("sky_phone:device:error", source, "operation_in_progress")
        return false
    end
    operation_locks[source] = true
    local slot = resolve_used_sim(source, used_item, item_name)
    if not slot then
        operation_locks[source] = nil
        TriggerClientEvent("sky_phone:device:error", source, "sim_slot_missing")
        return false
    end
    local sim, error_code = ensure_sim(source, slot, sim_type)
    if not sim then
        operation_locks[source] = nil
        TriggerClientEvent("sky_phone:device:error", source, error_code)
        return false
    end
    local choices, choice_error = list_phone_choices(source)
    if #choices == 0 then
        operation_locks[source] = nil
        TriggerClientEvent("sky_phone:device:error", source, choice_error or "phone_required")
        return false
    end
    pending_insertions[source] = {
        created_at = GetGameTimer(),
        item_name = item_name,
        sim = sim,
        slot = slot.slot,
    }
    operation_locks[source] = nil
    if #choices == 1 and not choices[1].occupied then
        return insert_sim(source, choices[1].imei, false).success
    end
    TriggerClientEvent("sky_phone:sim:picker", source, {
        choices = choices,
        number = sim.phone_number,
        phoneNumberFormat = {
            length = Config.Sim.NumberLength,
            groups = Config.Sim.NumberGroups,
        },
    })
    return true
end

-- Register the guarded callbacks in both modes so a resource-only restart can
-- replace registrations left behind in framework-owned usable-item tables.
local registered_sim_items = {}

local function register_configured_sim_items()
    for item_name in pairs(sim_types) do
        if not registered_sim_items[item_name] then
            local registered_item_name = item_name
            Bridge.Inventory.RegisterUsableItem(registered_item_name, function(...)
                if Config.Sim.Enabled ~= false and sim_types[registered_item_name] then
                    use_sim(...)
                end
            end)
            registered_sim_items[registered_item_name] = true
        end
    end
end

register_configured_sim_items()

Bridge.Callbacks.Register("sky_phone:sim:insert", function(source, data)
    if Config.Sim.Enabled == false then
        return { success = false, error = "disabled" }
    end
    if type(data) ~= "table" or not SkyPhoneImei.IsValid(data.imei) then
        return { success = false, error = "invalid_request" }
    end
    return insert_sim(source, data.imei, data.confirmed == true)
end)

Bridge.Callbacks.Register("sky_phone:sim:picker-close", function(source)
    if operation_locks[source] then
        return { success = false, error = "operation_in_progress" }
    end
    pending_insertions[source] = nil
    return { success = true }
end)

Bridge.Callbacks.Register("sky_phone:sim:eject", function(source)
    if Config.Sim.Enabled == false then
        return { success = false, error = "disabled" }
    end
    if operation_locks[source] then
        return { success = false, error = "operation_in_progress" }
    end
    local session, error_response = SkyPhone.RequireSession(source)
    if not session then
        return error_response
    end
    local device = SkyPhone.LoadDevice(session.imei)
    local sim = device and device.sim_id and load_sim(device.sim_id) or nil
    if not sim or tonumber(sim.is_virtual) == 1 then
        return { success = false, error = "no_sim" }
    end
    local phone_slot = Bridge.Inventory.GetSlot(source, session.slot)
    local item_name = sim.sim_type == "registered" and Config.Sim.RegisteredItem or Config.Sim.AnonymousItem
    local metadata = sim_metadata(sim)
    if not Bridge.Inventory.CanCarryItem(source, item_name, 1, metadata) then
        return { success = false, error = "inventory_full" }
    end

    operation_locks[source] = true
    if not set_phone_sim_metadata(source, phone_slot, nil) then
        operation_locks[source] = nil
        return { success = false, error = "metadata_unsupported" }
    end
    if not Bridge.Inventory.AddItem(source, item_name, 1, nil, metadata) then
        rollback_phone_metadata(source, phone_slot, sim)
        operation_locks[source] = nil
        return { success = false, error = "inventory_full" }
    end
    if affected_rows(Bridge.Database.Query("UPDATE `sky_phone_devices` SET `sim_id` = NULL WHERE `imei` = ? AND `sim_id` = ?", {
        session.imei,
        sim.id,
    })) ~= 1 then
        Bridge.Inventory.RemoveItem(source, item_name, 1, nil, metadata)
        rollback_phone_metadata(source, phone_slot, sim)
        operation_locks[source] = nil
        return { success = false, error = "request_failed" }
    end
    operation_locks[source] = nil
    SkyPhoneCompanies.ClearCallAvailability(source)
    SkyPhoneCalls.EndForSim(sim.id, "sim_removed")
    SkyPhone.RefreshDevice(session.imei)
    return { success = true }
end)

AddEventHandler("playerDropped", function()
    pending_insertions[source] = nil
    operation_locks[source] = nil
end)

AddEventHandler("sky_phone:configurator:serverUpdated", function()
    validate_number_configuration()
    refresh_sim_types()
    register_configured_sim_items()
end)

if Config.Sim.Enabled ~= false then
    Bridge.Database.Query([[
        UPDATE `sky_phone_devices` d
        INNER JOIN `sky_phone_sims` s ON s.`id` = d.`sim_id`
        SET d.`sim_id` = NULL
        WHERE s.`is_virtual` = 1
    ]], {})
end
end)
