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

# this script creates a build matrix for github actions from the claimed supported platforms and puppet versions in metadata.json

require 'json'

IMAGE_TABLE = {
  'RedHat-7' => 'rhel-7',
  'RedHat-8' => 'rhel-8',
  'RedHat-9' => 'rhel-9',
  'SLES-12' => 'sles-12',
  'SLES-15' => 'sles-15',
  'Windows-2016' => 'windows-2016',
  'Windows-2019' => 'windows-2019',
  'Windows-2022' => 'windows-2022',
}.freeze

DOCKER_PLATFORMS = {
  'CentOS-6' => 'litmusimage/centos:6',
  'CentOS-7' => 'litmusimage/centos:7',
  'CentOS-8' => 'litmusimage/centos:stream8', # Support officaly moved to Stream8, metadata is being left as is
  'Rocky-8' => 'litmusimage/rockylinux:8',
  'AlmaLinux-8' => 'litmusimage/almalinux:8',
  # 'Debian-8' => 'litmusimage/debian:8', Removing from testing: https://puppet.com/docs/pe/2021.0/supported_operating_systems.html
  'Debian-9' => 'litmusimage/debian:9',
  'Debian-10' => 'litmusimage/debian:10',
  'Debian-11' => 'litmusimage/debian:11',
  'OracleLinux-6' => 'litmusimage/oraclelinux:6',
  'OracleLinux-7' => 'litmusimage/oraclelinux:7',
  'Scientific-6' => 'litmusimage/scientificlinux:6',
  'Scientific-7' => 'litmusimage/scientificlinux:7',
  # 'Ubuntu-14.04' => 'litmusimage/ubuntu:14.04', Removing from testing: https://puppet.com/docs/pe/2021.0/supported_operating_systems.html
  # 'Ubuntu-16.04' => 'litmusimage/ubuntu:16.04', Support Dropped
  'Ubuntu-18.04' => 'litmusimage/ubuntu:18.04',
  'Ubuntu-20.04' => 'litmusimage/ubuntu:20.04',
  'Ubuntu-22.04' => 'litmusimage/ubuntu:22.04',
}.freeze

# This table uses the latest version in each collection for accurate
# comparison when evaluating puppet requirements from the metadata
COLLECTION_TABLE = [
  {
    puppet_maj_version: 6,
    ruby_version: 2.5,
  },
  {
    puppet_maj_version: 7,
    ruby_version: 2.7,
  },
].freeze

matrix = {
  platforms: [],
  collection: [],
}

spec_matrix = {
  include: [],
}

if ARGV.include?('--exclude-platforms')
  exclude_platforms_occurencies = ARGV.select { |arg| arg == '--exclude-platforms' }.length
  raise '--exclude-platforms argument should be present just one time in the command' unless exclude_platforms_occurencies <= 1

  exclude_platforms_list = ARGV[ARGV.find_index('--exclude-platforms') + 1]
  raise 'you need to provide a list of platforms in JSON format' if exclude_platforms_list.nil?

  begin
    exclude_list = JSON.parse(exclude_platforms_list).map { |platform| platform.downcase }
  rescue JSON::ParserError
    raise 'the exclude platforms list must valid JSON'
  end
else
  exclude_list = []
end

metadata_path = ENV['TEST_MATRIX_FROM_METADATA'] || 'metadata.json'
metadata = JSON.parse(File.read(metadata_path))
# Set platforms based on declared operating system support
metadata['operatingsystem_support'].sort_by { |a| a['operatingsystem'] }.each do |sup|
  os = sup['operatingsystem']
  sup['operatingsystemrelease'].sort_by { |a| a.to_i }.each do |ver|
    image_key = "#{os}-#{ver}"

    if IMAGE_TABLE.key?(image_key) && !exclude_list.include?(image_key.downcase)
      matrix[:platforms] << {
        label: image_key,
        provider: 'provision::provision_service',
        image: IMAGE_TABLE[image_key],
      }
    elsif DOCKER_PLATFORMS.key?(image_key) && !exclude_list.include?(image_key.downcase)
      matrix[:platforms] << {
        label: image_key,
        provider: 'provision::docker',
        image: DOCKER_PLATFORMS[image_key],
      }
    else
      puts "::warning::#{image_key} was excluded from testing" if exclude_list.include?(image_key.downcase)
      puts "::warning::Cannot find image for #{image_key}" unless exclude_list.include?(image_key.downcase)
    end
  end
end

# Set collections based on puppet version requirements
if metadata.key?('requirements') && metadata['requirements'].length.positive?
  metadata['requirements'].each do |req|
    next unless req.key?('name') && req.key?('version_requirement') && req['name'] == 'puppet'

    ver_regexp = %r{^([>=<]{1,2})\s*([\d.]+)\s+([>=<]{1,2})\s*([\d.]+)$}
    match = ver_regexp.match(req['version_requirement'])
    if match.nil?
      puts "::warning::Didn't recognize version_requirement '#{req['version_requirement']}'"
      break
    end

    cmp_one, ver_one, cmp_two, ver_two = match.captures
    reqs = ["#{cmp_one} #{ver_one}", "#{cmp_two} #{ver_two}"]

    COLLECTION_TABLE.each do |collection|
      # Test against the "largest" puppet version in a collection, e.g. `7.9999` to allow puppet requirements with a non-zero lower bound on minor/patch versions.
      # This assumes that such a boundary will always allow the latest actually existing puppet version of a release stream, trading off simplicity vs accuracy here.
      next unless Gem::Requirement.create(reqs).satisfied_by?(Gem::Version.new("#{collection[:puppet_maj_version]}.9999"))

      matrix[:collection] << "puppet#{collection[:puppet_maj_version]}-nightly"
      spec_matrix[:include] << { puppet_version: "~> #{collection[:puppet_maj_version]}.0", ruby_version: collection[:ruby_version] }
    end
  end
end

# Set to defaults (all collections) if no matches are found
if matrix[:collection].empty?
  matrix[:collection] = COLLECTION_TABLE.map { |collection| "puppet#{collection[:puppet_maj_version]}-nightly" }
end

# Just to make sure there aren't any duplicates
matrix[:platforms] = matrix[:platforms].uniq.sort { |a, b| a[:label] <=> b[:label] }
matrix[:collection] = matrix[:collection].uniq.sort

puts "::set-output name=matrix::#{JSON.generate(matrix)}"
puts "::set-output name=spec_matrix::#{JSON.generate(spec_matrix)}"

acceptance_test_cell_count = matrix[:platforms].length * matrix[:collection].length
spec_test_cell_count = spec_matrix[:include].length

puts "Created matrix with #{acceptance_test_cell_count + spec_test_cell_count} cells:"
puts "  - Acceptance Test Cells: #{acceptance_test_cell_count}"
puts "  - Spec Test Cells: #{spec_test_cell_count}"
