#! /usr/bin/env ruby
#
#  this script is intended to run as part of the CI test suite.
#
#  it inspects the contents of a gem file -- both the files and the gemspec -- to ensure we're
#  packaging what we expect, and that we're not packaging anything we don't expect.
#
#  this file isn't in the `test/` subdirectory because it's intended to be run standalone against a
#  built gem file (and not against the source code or behavior of the gem itself).
#
require "bundler/inline"

gemfile do
  source "https://rubygems.org"
  gem "minitest"
  gem "minitest-reporters"
end

require "yaml"

def usage_and_exit(message = nil)
  puts "ERROR: #{message}" if message
  puts "USAGE: #{File.basename(__FILE__)} <gemfile> [options]"
  exit(1)
end

usage_and_exit if ARGV.include?("-h")
usage_and_exit unless (gemfile = ARGV[0])
usage_and_exit("#{gemfile} does not exist") unless File.file?(gemfile)
usage_and_exit("#{gemfile} is not a gem") unless /\.gem$/.match?(gemfile)
gemfile = File.expand_path(gemfile)

gemfile_contents = Dir.mktmpdir do |dir|
  Dir.chdir(dir) do
    unless system("tar -xf #{gemfile} data.tar.gz")
      raise "could not unpack gem #{gemfile}"
    end

    `tar -ztf data.tar.gz`.split("\n")
  end
end

gemspec = Dir.mktmpdir do |dir|
  Dir.chdir(dir) do
    unless system("tar -xf #{gemfile} metadata.gz")
      raise "could not unpack gem #{gemfile}"
    end

    YAML.unsafe_load(`gunzip -c metadata.gz`)
  end
end

if ARGV.include?("-v")
  puts "---------- gemfile contents ----------"
  puts gemfile_contents
  puts
  puts "---------- gemspec ----------"
  puts gemspec.to_ruby
  puts
end

require "minitest/autorun"
require "minitest/reporters"
Minitest::Reporters.use!([Minitest::Reporters::SpecReporter.new])

puts "Testing '#{gemfile}' (#{gemspec.platform})"
describe File.basename(gemfile) do
  let(:supported_ruby_versions) { ["3.1", "3.2", "3.3", "3.4"] }

  describe "setup" do
    it "gemfile contains some files" do
      actual = gemfile_contents.length
      assert_operator(actual, :>, 10, "expected gemfile to contain more than #{actual} files")
    end

    it "gemspec is a Gem::Specification" do
      assert_equal(Gem::Specification, gemspec.class)
    end
  end

  describe "all platforms" do
    ["lib"].each do |dir|
      it "contains every ruby file in #{dir}/" do
        expected = `git ls-files #{dir}`.split("\n").grep(/\.rb$/).sort
        skip "looks like this isn't a git repository" if expected.empty?
        actual = gemfile_contents.select { |f| f.start_with?("#{dir}/") }.grep(/\.rb$/).sort
        assert_equal(expected, actual)
      end
    end

    ["test"].each do |dir|
      it "does not contain files from #{dir}/" do
        actual = gemfile_contents.select { |f| f.start_with?("#{dir}/") }.grep(/\.rb$/)
        assert_empty(actual)
      end
    end

    it "does not contain the Gemfile" do
      refute_includes(gemfile_contents, "Gemfile")
    end
  end

  if gemspec.platform == Gem::Platform::RUBY
    describe "ruby platform" do
      it "depends on mini_portile2" do
        assert(gemspec.dependencies.find { |d| d.name == "mini_portile2" })
      end

      it "contains extension C and header files" do
        assert_equal(6, gemfile_contents.count { |f| File.fnmatch?("ext/**/*.c", f) })
        assert_equal(7, gemfile_contents.count { |f| File.fnmatch?("ext/**/*.h", f) })
      end

      it "includes C files in extra_rdoc_files" do
        assert_equal(6, gemspec.extra_rdoc_files.count { |f| File.fnmatch?("ext/**/*.c", f) })
      end

      it "contains the port files" do
        dependencies = YAML.load_file(File.join(__dir__, "..", "dependencies.yml"), symbolize_names: true)
        sqlite_tarball = File.basename(dependencies[:sqlite3][:files].first[:url])
        actual_ports = gemfile_contents.grep(%r{^ports/})

        assert_equal(["ports/archives/#{sqlite_tarball}"], actual_ports)
      end

      it "contains the patch files" do
        assert_equal(Dir.glob("patches/*.patch").length, gemfile_contents.count { |f| File.fnmatch?("patches/*", f) })
      end

      it "sets metadata for msys2" do
        refute_nil(gemspec.metadata["msys2_mingw_dependencies"])
      end

      it "sets required_ruby_version appropriately" do
        supported_ruby_versions.each do |v|
          assert(
            gemspec.required_ruby_version.satisfied_by?(Gem::Version.new(v)),
            "required_ruby_version='#{gemspec.required_ruby_version}' should support ruby #{v}"
          )
        end
      end
    end
  end

  if gemspec.platform.is_a?(Gem::Platform) && gemspec.platform.cpu
    describe "native platform" do
      it "does not depend on mini_portile2" do
        refute(gemspec.dependencies.find { |d| d.name == "mini_portile2" })
      end

      it "contains extension C and header files" do
        assert_equal(6, gemfile_contents.count { |f| File.fnmatch?("ext/**/*.c", f) })
        assert_equal(7, gemfile_contents.count { |f| File.fnmatch?("ext/**/*.h", f) })
      end

      it "includes C files in extra_rdoc_files" do
        assert_equal(6, gemspec.extra_rdoc_files.count { |f| File.fnmatch?("ext/**/*.c", f) })
      end

      it "does not contain the port files" do
        assert_empty(gemfile_contents.grep(%r{^ports/}))
      end

      it "does not contain the patch files" do
        assert_empty(gemfile_contents.grep(%r{^patches/}))
      end

      it "contains expected shared library files " do
        supported_ruby_versions.each do |version|
          actual = gemfile_contents.find do |p|
            File.fnmatch?("lib/sqlite3/#{version}/sqlite3_native.{so,bundle}", p, File::FNM_EXTGLOB)
          end
          assert(actual, "expected to find shared library file for ruby #{version}")
        end

        actual = gemfile_contents.find do |p|
          File.fnmatch?("lib/sqlite3/sqlite3_native.{so,bundle}", p, File::FNM_EXTGLOB)
        end
        refute(actual, "did not expect to find shared library file in lib/sqlite3")

        actual = gemfile_contents.find_all do |p|
          File.fnmatch?("lib/sqlite3/**/*.{so,bundle}", p, File::FNM_EXTGLOB)
        end
        assert_equal(
          supported_ruby_versions.length,
          actual.length,
          "did not expect extra shared library files"
        )
      end

      it "sets required_ruby_version appropriately" do
        supported_ruby_versions.each do |v|
          assert(
            gemspec.required_ruby_version.satisfied_by?(Gem::Version.new(v)),
            "required_ruby_version='#{gemspec.required_ruby_version}' should support ruby #{v}"
          )
        end
      end

      it "does not set metadata for msys2" do
        assert_nil(gemspec.metadata["msys2_mingw_dependencies"])
      end
    end
  end
end
