# frozen_string_literal: true

module Rspec
  module PendingFor
    # SRP: Describe the RubyEngine and/or RubyVersion(s) that will be pended or skipped and with what message
    class Build
      #
      # | RUBY_ENGINE    | Implementation           |
      # |:--------------:|:------------------------:|
      # | "unknown"      | MRI < 1.9 (probably)     |
      # | "ruby"         | MRI >= 1.9               |
      # | "ree"          | Ruby Enterprise Edition  |
      # | "jruby"        | JRuby                    |
      # | "macruby"      | MacRuby                  |
      # | "rbx"          | Rubinius                 |
      # | "maglev"       | MagLev                   |
      # | "ironruby"     | IronRuby                 |
      # | "cardinal"     | Cardinal                 |
      # | "truffleruby"  | Truffle Ruby             |
      #

      # Keys are the
      INTERPRETER_MATRIX = {
        "unknown" => "MRI < 1.9 (probably)",
        "ruby" => "MRI >= 1.9",
        "ree" => "Ruby Enterprise Edition",
        "jruby" => "JRuby",
        "macruby" => "MacRuby",
        "rbx" => "Rubinius",
        "maglev" => "MagLev",
        "ironruby" => "IronRuby",
        "cardinal" => "Cardinal",
        "truffleruby" => "Truffle Ruby"
      }.freeze
      BROKEN_STRING = "Behavior is broken"
      BUG_STRING = "due to a bug in the Ruby engine"
      VERSIONS_STRING = "in Ruby versions"
      ISSUES_LINK = "https://github.com/galtzo-floss/rspec-pending_for/issues"
      RELEVANT_VERSIONS_PROC = lambda { |rv| "#{BROKEN_STRING} #{VERSIONS_STRING} #{rv} #{BUG_STRING}" }

      attr_reader :message, :relevant_versions, :relevant_engine, :reason

      def initialize(options = {})
        # Normalize versions without enumerating ranges
        raw_versions = options[:versions]
        @relevant_versions = if raw_versions.nil?
          []
        elsif raw_versions.is_a?(Array)
          raw_versions
        elsif raw_versions.is_a?(Range)
          [raw_versions]
        else
          [raw_versions]
        end
        @relevant_engine = options[:engine].nil? ? nil : options[:engine].to_s
        @reason = options[:reason]
        warn_about_unrecognized_engine
        # If engine is nil, then any matching versions should be pended
        @message = if @relevant_engine.nil?
          no_engine_specified
        elsif RubyEngine.is?(@relevant_engine)
          engine_specified_and_relevant
        end
      end

      def current_matches_specified?
        !message.nil?
      end

      private

      # Determine whether the current Ruby version matches any of the provided version specs.
      # A version spec may be:
      # - String: exact match against RubyVersion.to_s, or "head" for dev/SNAPSHOT builds
      # - Range[Gem::Version, Gem::Version]: inclusive/exclusive respected
      # - Range[Integer, Integer]: compares major version from RubyVersion.to_s
      def versions_include_current?
        return false if relevant_versions.nil?

        current_str = RubyVersion.to_s
        current_major = current_str.to_s.split(".").first.to_i
        current_gemv = begin
          Gem::Version.new(current_str.to_s)
        rescue
          nil
        end

        relevant_versions.any? do |spec|
          case spec
          when String
            next head_build? if spec == "head"

            # Support minor-version shorthand like "3.1" to match any 3.1.x
            if spec.to_s =~ /^\d+\.\d+$/
              current_major_minor = current_str.to_s.split(".")[0, 2].join(".")
              spec == current_major_minor
            else
              spec == current_str
            end
          when Range
            b = spec.begin
            e = spec.end
            if b.is_a?(Gem::Version) && e.is_a?(Gem::Version)
              next false unless current_gemv
              # Respect exclusive end
              if spec.exclude_end?
                b <= current_gemv && current_gemv < e
              else
                current_gemv.between?(b, e)
              end
            elsif b.is_a?(Integer) && e.is_a?(Integer)
              if spec.exclude_end?
                b <= current_major && current_major < e
              else
                current_major.between?(b, e)
              end
            else
              # Fallback: try cover? with the string form (likely false if incomparable)
              spec.respond_to?(:cover?) && spec.cover?(current_str)
            end
          else
            false
          end
        end
      end

      def head_build?
        RUBY_DESCRIPTION.to_s.match?(/\b(?:dev|head|SNAPSHOT)\b/i)
      end

      def warn_about_unrecognized_engine
        return false if relevant_engine.nil? || !INTERPRETER_MATRIX[relevant_engine].nil?

        warn(%[
Engine specified (#{relevant_engine}) is not known to rspec-pending_for.
If it is a real RUBY_ENGINE, please report as a bug to #{ISSUES_LINK}
])
      end

      def no_engine_specified
        reason || RELEVANT_VERSIONS_PROC.call(relevant_versions) if versions_include_current?
      end

      def engine_specified_and_relevant
        if relevant_versions.empty?
          # No versions specified means ALL versions for this engine
          reason || "#{BROKEN_STRING} #{BUG_STRING} #{INTERPRETER_MATRIX[relevant_engine]}"
        elsif versions_include_current?
          reason || %[#{RELEVANT_VERSIONS_PROC.call(relevant_versions)} (#{INTERPRETER_MATRIX[relevant_engine]})]
        end
      end
    end
  end
end
