Updater = {}

Updater.maxRetries = 3

local updaterWindow
local loadModulesFunction
local scheduledEvent
local httpOperationId = 0

local function onLog(level, message, time)
  if level == LogError then
    Updater.error(message)
    g_logger.setOnLog(nil)
  end
end

local function loadModules()
  if loadModulesFunction then
    local tmpLoadFunc = loadModulesFunction
    loadModulesFunction = nil
    tmpLoadFunc()
  end
end

local function downloadPackage(url, checksum, retries)
  if not updaterWindow then return end

  if retries > 0 then
    updaterWindow.downloadStatus:setText(tr("Downloading update (%i retry)...", retries))
  else
    updaterWindow.downloadStatus:setText(tr("Downloading update..."))
  end
  updaterWindow.downloadProgress:setPercent(0)

  local downloadKey = "update-package.zip"
  httpOperationId = HTTP.download(url, downloadKey,
    function(path, dlChecksum, err)
      if not updaterWindow then return end
      if err then
        if retries >= Updater.maxRetries then
          Updater.error("Download failed: " .. err)
        else
          scheduledEvent = scheduleEvent(function()
            downloadPackage(url, checksum, retries + 1)
          end, 1000)
        end
        return
      end

      -- Validate checksum
      if checksum and dlChecksum ~= checksum then
        Updater.error("Checksum mismatch.\nExpected: " .. checksum .. "\nGot: " .. dlChecksum)
        return
      end

      -- Extract the package
      updaterWindow.downloadStatus:setText(tr("Extracting update..."))
      updaterWindow.downloadProgress:setPercent(100)
      updaterWindow.mainProgress:setPercent(90)

      scheduledEvent = scheduleEvent(function()
        if not updaterWindow then return end

        local success = g_resources.extractZipUpdate(downloadKey)
        if not success then
          Updater.error("Failed to extract update package.")
          return
        end

        updaterWindow.mainProgress:setPercent(100)
        updaterWindow.downloadStatus:setText(tr("Update complete! Restarting..."))

        scheduledEvent = scheduleEvent(function()
          g_app.restart()
        end, 500)
      end, 100)
    end,
    function(progress, speed)
      if not updaterWindow then return end
      updaterWindow.downloadProgress:setPercent(progress)
      updaterWindow.downloadProgress:setText(speed .. " kbps")
      updaterWindow.mainProgress:setPercent(math.floor(progress * 0.9))
    end)
end

local function handleUpdateResponse(data)
  if not updaterWindow then return end

  if type(data) ~= "table" then
    return Updater.error("Invalid data from updater api (not table)")
  end

  if type(data.error) == 'string' and data.error:len() > 0 then
    return Updater.error(data.error)
  end

  -- Requires full reinstall (binary/data update)
  if data.requires_reinstall == true then
    updaterWindow.status:setText(tr("New version v%s available", data.version or "?"))
    updaterWindow.downloadStatus:setText(tr("This update requires a full download.\nPlease download the new client from the website."))
    updaterWindow.downloadStatus:show()
    updaterWindow.downloadProgress:hide()
    updaterWindow.changeUrlButton:hide()
    updaterWindow.cancelButton:hide()

    updaterWindow.downloadButton:show()
    updaterWindow.downloadButton.onClick = function()
      g_platform.openUrl(data.download_page or 'https://vexiaonline.cl/index.php/downloads')
    end
    updaterWindow.exitButton:show()
    updaterWindow.exitButton.onClick = function()
      g_app.exit()
    end
    return
  end

  -- New package-based update
  if data.update == true and data.package_url then
    updaterWindow.status:setText(tr("Update available: v%s", data.version or "?"))
    updaterWindow.mainProgress:setPercent(0)
    updaterWindow.downloadProgress:setPercent(0)
    updaterWindow.downloadProgress:show()
    updaterWindow.downloadStatus:show()
    updaterWindow.changeUrlButton:hide()

    downloadPackage(data.package_url, data.package_checksum, 0)
    return
  end

  -- Backward compat: old per-file update (if server hasn't been updated yet)
  if data.files and type(data.files) == 'table' then
    local hasFiles = false
    for _ in pairs(data.files) do hasFiles = true; break end
    if hasFiles and type(data.url) == 'string' and data.url:len() >= 4 then
      return updateFilesCompat(data)
    end
  end

  -- No update needed
  updaterWindow.mainProgress:setPercent(100)
  scheduledEvent = scheduleEvent(Updater.abort, 20)
end

-- Backward compatibility: old per-file update for transitional period
function updateFilesCompat(data)
  if not updaterWindow then return end

  local localFiles = g_resources.filesChecksums()
  local toUpdate = {}
  local toUpdateFiles = {}

  for file, checksum in pairs(data.files) do
    if not localFiles[file] or localFiles[file] ~= checksum then
      table.insert(toUpdate, { file, checksum })
      table.insert(toUpdateFiles, file)
    end
  end

  if #toUpdate == 0 then
    updaterWindow.mainProgress:setPercent(100)
    scheduledEvent = scheduleEvent(Updater.abort, 20)
    return
  end

  local function downloadFiles(url, files, index, retries, doneCallback)
    if not updaterWindow then return end
    local entry = files[index]
    if not entry then return doneCallback() end
    local file = entry[1]
    local file_checksum = entry[2]

    updaterWindow.downloadStatus:setText(tr("Downloading:\n%s", file))
    updaterWindow.downloadProgress:setPercent(0)
    updaterWindow.mainProgress:setPercent(math.floor(100 * index / #files))

    httpOperationId = HTTP.download(url .. file, file,
      function(f, cs, err)
        if not err and cs ~= file_checksum then
          err = "Invalid checksum of: " .. file
        end
        if err then
          if retries >= 5 then
            Updater.error("Can't download file: " .. file .. ".\nError: " .. err)
          else
            scheduledEvent = scheduleEvent(function()
              downloadFiles(url, files, index, retries + 1, doneCallback)
            end, 250)
          end
          return
        end
        downloadFiles(url, files, index + 1, 0, doneCallback)
      end,
      function(progress, speed)
        updaterWindow.downloadProgress:setPercent(progress)
        updaterWindow.downloadProgress:setText(speed .. " kbps")
      end)
  end

  updaterWindow.status:setText(tr("Updating %i files", #toUpdate))
  updaterWindow.mainProgress:setPercent(0)
  updaterWindow.downloadProgress:show()
  updaterWindow.downloadStatus:show()
  updaterWindow.changeUrlButton:hide()

  downloadFiles(data.url, toUpdate, 1, 0, function()
    updaterWindow.status:setText(tr("Updating client..."))
    updaterWindow.mainProgress:setPercent(100)
    updaterWindow.downloadProgress:hide()
    updaterWindow.downloadStatus:hide()
    scheduledEvent = scheduleEvent(function()
      g_resources.updateFiles(toUpdateFiles)
      g_app.restart()
    end, 100)
  end)
end

-- public functions
function Updater.init(loadModulesFunc)
  g_logger.setOnLog(onLog)
  loadModulesFunction = loadModulesFunc
  Updater.check()
end

function Updater.terminate()
  loadModulesFunction = nil
  Updater.abort(true)
end

function Updater.abort(terminate)
  HTTP.cancel(httpOperationId)
  removeEvent(scheduledEvent)
  if updaterWindow then
    updaterWindow:destroy()
    updaterWindow = nil
  end
  loadModules()
  if not terminate then
    signalcall(g_app.onUpdateFinished, g_app)
  end
end

function Updater.check(args)
  if updaterWindow then return end

  updaterWindow = g_ui.displayUI('updater')
  updaterWindow:show()
  updaterWindow:focus()
  updaterWindow:raise()

  -- Display current version
  local versionLabel = updaterWindow:getChildById('versionLabel')
  if versionLabel then
    versionLabel:setText('Client v' .. (APP_VERSION or '0.0.0'))
  end

  local updateData = nil
  local function progressUpdater(value)
    removeEvent(scheduledEvent)
    if value == 100 then
      return Updater.error(tr("Timeout"))
    end
    if updateData and (value > 60 or (not g_platform.isMobile() or not ALLOW_CUSTOM_SERVERS or not loadModulesFunc)) then
      return handleUpdateResponse(updateData)
    end
    scheduledEvent = scheduleEvent(function() progressUpdater(value + 1) end, 100)
    updaterWindow.mainProgress:setPercent(value)
  end
  progressUpdater(0)

  httpOperationId = HTTP.postJSON(Services.updater, {
    version = APP_VERSION or "0.0.0",
    build = g_app.getVersion(),
    revision = g_app.getBuildRevision(),
    os = g_app.getOs(),
    platform = g_window.getPlatformType(),
    args = args or {}
  }, function(data, err)
    if err then
      return Updater.error(err)
    end
    updateData = data
  end)
end

function Updater.error(message)
  removeEvent(scheduledEvent)
  if not updaterWindow then return end
  displayErrorBox(tr("Updater Error"), message).onOk = function()
    Updater.abort()
  end
end
