#!/usr/bin/env ruby
# frozen_string_literal: true

# Download and install the latest ASL3 Asterisk runtime packages.
# Default mirror: https://anarchy.w5gle.net/asl3-alsa/{arm64,x86_64}/
#
# Usage:
#   sudo ./asl3-alsa-update.rb
#   ./asl3-alsa-update.rb --dry-run

require 'fileutils'
require 'open-uri'
require 'optparse'
require 'shellwords'
require 'tmpdir'
require 'uri'

BASE_URL = ENV.fetch('ASL3_ALSA_URL', 'https://anarchy.w5gle.net/asl3-alsa').freeze

def arch_layout
  case RbConfig::CONFIG['host_cpu']
  when 'aarch64', 'arm64'
    { dir: 'arm64', deb_arch: 'arm64' }
  when 'x86_64', 'amd64'
    { dir: 'x86_64', deb_arch: 'amd64' }
  else
    abort "Unsupported architecture: #{RbConfig::CONFIG['host_cpu']}"
  end
end

def fetch_index(base_url, dir)
  index_url = "#{base_url}/#{dir}/"
  warn "Fetching package index from #{index_url}..."
  URI(index_url).open(read_timeout: 60, open_timeout: 30).read
end

def deb_links(html)
  html.scan(/href="([^"]*asl3-asterisk[^"?]+\.deb)"/).flatten.map do |href|
    URI::DEFAULT_PARSER.unescape(File.basename(href))
  end.uniq
end

def release_number(filename)
  filename[/-(\d+)\.deb\d+/, 1]&.to_i || 0
end

def newest(debs, pattern, label, dir)
  matches = debs.select { |f| f.match?(pattern) }
  abort "No #{label} package found at #{BASE_URL}/#{dir}/" if matches.empty?

  matches.max_by { |f| [release_number(f), f] }
end

def download(url, dest)
  warn "Downloading #{File.basename(url)}..."
  URI(url).open(read_timeout: 300, open_timeout: 30) do |io|
    File.open(dest, 'wb') { |f| IO.copy_stream(io, f) }
  end
end

def run!(*args)
  warn "Running: #{args.shelljoin}"
  system(*args) || abort("Command failed: #{args.shelljoin}")
end

options = { dry_run: false, staging: nil }
OptionParser.new do |opts|
  opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options]"
  opts.on('--dry-run', 'Show packages that would be installed') { options[:dry_run] = true }
  opts.on('--staging DIR', 'Use pre-downloaded packages (internal)') { |dir| options[:staging] = dir }
  opts.on('-h', '--help', 'Show this help') do
    puts opts
    exit
  end
end.parse!

layout = arch_layout
index = fetch_index(BASE_URL, layout[:dir])
debs = deb_links(index)

config_deb = newest(debs, /\Aasl3-asterisk-config_.+_all\.deb\z/, 'config', layout[:dir])
modules_deb = newest(debs, /\Aasl3-asterisk-modules_.+_#{layout[:deb_arch]}\.deb\z/, 'modules', layout[:dir])
main_deb = newest(debs, /\Aasl3-asterisk_\d.+_#{layout[:deb_arch]}\.deb\z/, 'main', layout[:dir])

packages = [config_deb, modules_deb, main_deb]
warn "Selected (#{layout[:dir]}):"
packages.each { |pkg| warn "  #{pkg}" }

if options[:dry_run]
  warn 'Dry run; no changes made.'
  exit 0
end

staging = options[:staging]
unless Process.euid.zero?
  staging = Dir.mktmpdir('asl3-alsa-update')
  packages.each do |name|
    download("#{BASE_URL}/#{layout[:dir]}/#{name}", File.join(staging, name))
  end
  exec('sudo', '-E', $PROGRAM_NAME, '--staging', staging)
end

staging = Dir.mktmpdir('asl3-alsa-update') if staging.nil?
begin
  unless options[:staging]
    packages.each do |name|
      download("#{BASE_URL}/#{layout[:dir]}/#{name}", File.join(staging, name))
    end
  end

  paths = packages.map { |name| File.join(staging, name) }
  paths.each { |path| abort "Missing package: #{path}" unless File.file?(path) }

  ENV['DEBIAN_FRONTEND'] = 'noninteractive'
  run!('dpkg', '--force-confold', '-i', *paths)

  oss_conf = '/etc/modules-load.d/asl3-oss.conf'
  if File.exist?(oss_conf)
    warn "Removing #{oss_conf}"
    FileUtils.rm_f(oss_conf)
  end

  run!('systemctl', 'restart', 'asterisk')
  warn 'Done. Asterisk restarted.'
ensure
  FileUtils.remove_entry_secure(staging) if staging && Dir.exist?(staging)
end
