# frozen_string_literal: true

require 'digest'

module Overcommit::HookLoader
  # Responsible for loading hooks that are specific to the repository Overcommit
  # is running in.
  class PluginHookLoader < Base
    def load_hooks
      check_for_modified_plugins if @config.verify_signatures?

      hooks = plugin_paths.map do |plugin_path|
        require plugin_path

        hook_name = Overcommit::Utils.camel_case(File.basename(plugin_path, '.rb'))
        create_hook(hook_name)
      end

      hooks + ad_hoc_hook_names.map do |hook_name|
        create_ad_hoc_hook(hook_name)
      end
    end

    def update_signatures
      log.success('No plugin signatures have changed') if modified_plugins.empty?

      modified_plugins.each do |plugin|
        plugin.update_signature!
        log.warning "Updated signature of plugin #{plugin.hook_name}"
      end
    end

    private

    def plugin_paths
      directory = File.join(@config.plugin_directory, @context.hook_type_name)
      Dir[File.join(directory, '*.rb')].sort
    end

    def plugin_hook_names
      plugin_paths.map do |path|
        Overcommit::Utils.camel_case(File.basename(path, '.rb'))
      end
    end

    def ad_hoc_hook_names
      @config.enabled_ad_hoc_hooks(@context)
    end

    def modified_plugins
      (plugin_hook_names + ad_hoc_hook_names).
        map { |hook_name| Overcommit::HookSigner.new(hook_name, @config, @context) }.
        select(&:signature_changed?)
    end

    def check_for_modified_plugins
      return if modified_plugins.empty?

      log.bold_warning "The following #{@context.hook_script_name} plugins " \
                       'have been added, changed, or had their configuration modified:'
      log.newline

      modified_plugins.each do |signer|
        log.warning " * #{signer.hook_name} in #{signer.hook_path}"
      end

      log.newline
      log.bold_warning 'You should verify the changes and then run:'
      log.newline
      log.warning "overcommit --sign #{@context.hook_script_name}"
      log.newline
      log.log "For more information, see #{Overcommit::REPO_URL}#security"

      raise Overcommit::Exceptions::InvalidHookSignature
    end

    def create_ad_hoc_hook(hook_name)
      hook_module = Overcommit::Hook.const_get(@context.hook_class_name)
      hook_base = hook_module.const_get('Base')

      # Implement a simple class that executes the command and returns pass/fail
      # based on the exit status
      hook_class = Class.new(hook_base) do
        def run
          result = @context.execute_hook(command)

          if result.success?
            [:pass, result.stdout]
          else
            [:fail, result.stdout + result.stderr]
          end
        end
      end

      hook_module.const_set(hook_name, hook_class).new(@config, @context)
    rescue LoadError, NameError => e
      raise Overcommit::Exceptions::HookLoadError,
            "Unable to load hook '#{hook_name}': #{e}",
            e.backtrace
    end
  end
end
