#!/usr/bin/env ruby

require "fileutils"

gem_home = Gem.paths.home

puts "fixing bundle installed gems in #{gem_home}"

# Install gems from git repos.  This makes the assumption that there is a <gem_name>.gemspec and
# you can simply gem build + gem install the resulting gem, so nothing fancy.  This does not use
# rake install since we need --conservative --minimal-deps in order to not install duplicate gems.
#
bundler_gems_path = File.join(gem_home, "bundler", "gems", "*")
puts "Looking for git gems in: #{bundler_gems_path}"

Dir[bundler_gems_path].each do |gempath|
  puts "Found gem path: #{gempath}"
  matches = File.basename(gempath).match(/.*-[A-Fa-f0-9]{12}/)
  next unless matches

  gemspec_files = Dir[File.join(gempath, "*.gemspec")]
  puts "Found gemspec files: #{gemspec_files.inspect}"

  next if gemspec_files.empty?

  gem_name = File.basename(gemspec_files.first, ".gemspec")
  # FIXME: should strip any valid ruby platform off of the gem_name if it matches

  next unless gem_name

  puts "re-installing #{gem_name}..."

  Dir.chdir(gempath) do
    # Use full path to gem command for Windows compatibility
    gem_cmd = File.join(RbConfig::CONFIG["bindir"], "gem")

    build_cmd = "\"#{gem_cmd}\" build #{gem_name}.gemspec"
    puts "Running: #{build_cmd}"
    raise "gem build failed for #{gem_name}" unless system(build_cmd)

    install_cmd = "\"#{gem_cmd}\" install #{gem_name}*.gem --conservative --minimal-deps --no-document"
    puts "Running: #{install_cmd}"
    raise "gem install failed for #{gem_name}" unless system(install_cmd)
  end

  # Verify the gem was installed using the correct gem command (not PATH's gem)
  gem_cmd = File.join(RbConfig::CONFIG["bindir"], "gem")
  puts "Verifying #{gem_name} installation..."
  # Use --exact to match only the specific gem name (avoids 'chef' matching 'chef-utils')
  installed_gems = `"#{gem_cmd}" list --exact #{gem_name}`.chomp
  if installed_gems.include?(gem_name)
    puts "#{gem_name} installed successfully: #{installed_gems}"

    # Appbundler rewrites git gems to local path sources while generating merged
    # lockfiles. Path sources require a gemspec inside the gem directory.
    # Build/install from a git checkout does not always leave one there.
    installed_spec = Gem::Specification.find_all_by_name(gem_name).max_by(&:version)
    if installed_spec
      installed_gemspec = File.join(installed_spec.full_gem_path, "#{installed_spec.name}.gemspec")
      unless File.exist?(installed_gemspec)
        File.write(installed_gemspec, installed_spec.to_ruby)
        puts "Wrote static gemspec for #{gem_name} to #{installed_gemspec}"
      end

      # Appbundler reads the gem's Gemfile when --without groups are specified.
      # Regular gem install does not copy the Gemfile, so copy it from the git
      # checkout if present.
      src_gemfile = File.join(gempath, "Gemfile")
      dst_gemfile = File.join(installed_spec.full_gem_path, "Gemfile")
      if File.exist?(src_gemfile) && !File.exist?(dst_gemfile)
        FileUtils.cp(src_gemfile, dst_gemfile)
        puts "Copied Gemfile for #{gem_name} to #{dst_gemfile}"
      end
    else
      raise "Unable to locate installed spec for #{gem_name}"
    end
  else
    raise "#{gem_name} installation verification failed!"
  end
end

# Handle default gem conflicts with bundled gems
# CVE-2025-24294: resolv 0.2.1 has a security vulnerability
default_gem_list = {
  resolv: "0.2.1",
}

default_gem_list.each do |gem_name, version|
  puts "Checking #{gem_name} gem installation..."
  gem_info = `gem info #{gem_name}`

  # Check if the old default version exists
  if gem_info.include?("default):") && gem_info.match?(/#{gem_name} \([0-9., ]*#{version}[0-9., ]*\)/)
    puts "Found default #{gem_name} (#{version}), removing gemspec and upgrading..."

    # Windows: Ruby runs from omnibus-toolchain during build, need to check all gem paths
    # Linux/macOS: Extract path directly from gem info output
    if RUBY_PLATFORM =~ /mswin|mingw|windows/
      Gem.path.each do |gem_path|
        gemspec_path = File.join(gem_path, "specifications", "default", "#{gem_name}-#{version}.gemspec")

        if File.exist?(gemspec_path)
          puts "Removing default #{gem_name} gemspec: #{gemspec_path}"
          File.delete(gemspec_path)
        end
      end
    else
      # Extract the default gem path from gem info output
      default_path = gem_info.match(/default\): (.+)$/)[1]

      if default_path
        gemspec_path = File.join(default_path.strip, "specifications", "default", "#{gem_name}-#{version}.gemspec")

        if File.exist?(gemspec_path)
          puts "Removing default #{gem_name} gemspec: #{gemspec_path}"
          File.delete(gemspec_path)
        end
      end
    end

    # Install the newer version to the embedded gem path
    puts "Installing #{gem_name} gem to #{gem_home}..."
    system("gem install #{gem_name} -v 0.2.3 --install-dir #{gem_home} --no-document") || raise("gem install #{gem_name} failed")
    puts "#{gem_name} gem installed successfully"
  else
    puts "#{gem_name} (#{version}) not found as default gem, skipping"
  end
end
