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

#
# Wrapper for the CodeClimate integration.

require_relative '../lib/reek'
require_relative '../lib/reek/cli/application'
require_relative '../lib/reek/code_climate'
Reek::CLI::Silencer.silently { require 'parser/current' }

module CodeClimateReek
  # Map input coming from CodeClimate to Reek.
  class CodeClimateToReek
    # Following the spec (https://github.com/codeclimate/spec/blob/master/SPEC.md)
    # we have to exit with a zero for both failure and success.
    ENGINE_CONFIGURATION = [
      '--failure-exit-code', '0',
      '--success-exit-code', '0'
    ].freeze
    CUSTOM_CONFIG_KEY = 'config'
    CUSTOM_CONFIG_TARGET_RUBY_VERSION_KEY = 'target_ruby_version'

    attr_reader :configuration_file_path, :include_paths_key, :include_paths_default

    def initialize(configuration_file_path: '/config.json',
                   include_paths_key: 'include_paths',
                   include_paths_default: ['.'])
      @configuration_file_path = configuration_file_path
      @include_paths_key       = include_paths_key
      @include_paths_default   = include_paths_default
    end

    def cli_arguments
      include_paths + ENGINE_CONFIGURATION
    end

    def target_ruby_version
      config.dig(CUSTOM_CONFIG_KEY, CUSTOM_CONFIG_TARGET_RUBY_VERSION_KEY)
    end

    private

    def configuration_file_exists?
      Pathname.new(configuration_file_path).exist?
    end

    # The config.json file we try to read below might look like this:
    # {
    #   "include_paths":[
    #     "lib",
    #     "spec"
    #   ]
    # }
    def include_paths
      config.fetch include_paths_key, include_paths_default
    end

    def config
      if configuration_file_exists?
        JSON.parse File.read(configuration_file_path)
      else
        {}
      end
    end
  end

  # Override for ReportCommand to force the use of CodeClimateReport.
  module ReportClassOverride
    def report_class
      Reek::CodeClimate::CodeClimateReport
    end
  end

  # Override Reek::Source::SourceCode to use a parser version specified by the user
  module SourceCodeOverride
    # override self.default_parser method
    def default_parser
      parser_class.new(Reek::AST::Builder.new).tap do |parser|
        diagnostics = parser.diagnostics
        diagnostics.all_errors_are_fatal = true
        diagnostics.ignore_warnings      = true
      end
    end

    # config.json file will look like this:
    # {
    #   "include_paths":[
    #     "lib",
    #     "spec"
    #   ],
    #   "config": {
    #     "target_ruby_version": "3.1.0"
    #   }
    # }
    def parser_class
      # convert an X.Y.Z version number to an XY two digit number
      requested_version = CodeClimateToReek.new.target_ruby_version
      return Parser::CurrentRuby if requested_version.nil?

      version_number = Gem::Version.new(requested_version).segments[0..1].join

      begin
        Reek::CLI::Silencer.silently { require "parser/ruby#{version_number}" }
        Module.const_get("Parser::Ruby#{version_number}")
      rescue LoadError, NameError
        # use Parser::CurrentRuby when an invalid version number is provided
        Parser::CurrentRuby
      end
    end
  end
end

Reek::CLI::Command::ReportCommand.prepend CodeClimateReek::ReportClassOverride
Reek::Source::SourceCode.singleton_class.prepend CodeClimateReek::SourceCodeOverride

application = Reek::CLI::Application.new(CodeClimateReek::CodeClimateToReek.new.cli_arguments)

exit application.execute
