require "capistrano/dsl/task_enhancements"
require "capistrano/dsl/paths"
require "capistrano/dsl/stages"
require "capistrano/dsl/env"
require "capistrano/configuration/filter"

module Capistrano
  module DSL
    include TaskEnhancements
    include Env
    include Paths
    include Stages

    def invoke(task_name, *args)
      task = Rake::Task[task_name]
      # NOTE: We access instance variable since the accessor was only added recently. Once Capistrano depends on rake 11+, we can revert the following line
      if task && task.instance_variable_get(:@already_invoked)
        file, line, = caller.first.split(":")
        colors = SSHKit::Color.new($stderr)
        $stderr.puts colors.colorize("Skipping task `#{task_name}'.", :yellow)
        $stderr.puts "Capistrano tasks may only be invoked once. Since task `#{task}' was previously invoked, invoke(\"#{task_name}\") at #{file}:#{line} will be skipped."
        $stderr.puts "If you really meant to run this task again, use invoke!(\"#{task_name}\")"
        $stderr.puts colors.colorize("THIS BEHAVIOR MAY CHANGE IN A FUTURE VERSION OF CAPISTRANO. Please join the conversation here if this affects you.", :red)
        $stderr.puts colors.colorize("https://github.com/capistrano/capistrano/issues/1686", :red)
      end
      task.invoke(*args)
    end

    def invoke!(task_name, *args)
      task = Rake::Task[task_name]
      task.reenable
      task.invoke(*args)
    end

    def t(key, options={})
      I18n.t(key, **options.merge(scope: :capistrano))
    end

    def scm
      fetch(:scm)
    end

    def sudo(*args)
      execute :sudo, *args
    end

    def revision_log_message
      fetch(:revision_log_message,
            t(:revision_log_message,
              branch: fetch(:branch),
              user: local_user,
              sha: fetch(:current_revision),
              release: fetch(:release_timestamp)))
    end

    def rollback_log_message
      t(:rollback_log_message, user: local_user, release: fetch(:rollback_timestamp))
    end

    def local_user
      fetch(:local_user)
    end

    def lock(locked_version)
      VersionValidator.new(locked_version).verify
    end

    # rubocop:disable Security/MarshalLoad
    def on(hosts, options={}, &block)
      subset_copy = Marshal.dump(Configuration.env.filter(hosts))
      SSHKit::Coordinator.new(Marshal.load(subset_copy)).each(options, &block)
    end
    # rubocop:enable Security/MarshalLoad

    def run_locally(&block)
      SSHKit::Backend::Local.new(&block).run
    end

    # Catch common beginner mistake and give a helpful error message on stderr
    def execute(*)
      file, line, = caller.first.split(":")
      colors = SSHKit::Color.new($stderr)
      $stderr.puts colors.colorize("Warning: `execute' should be wrapped in an `on' scope in #{file}:#{line}.", :red)
      $stderr.puts
      $stderr.puts "  task :example do"
      $stderr.puts colors.colorize("    on roles(:app) do", :yellow)
      $stderr.puts "      execute 'whoami'"
      $stderr.puts colors.colorize("    end", :yellow)
      $stderr.puts "  end"
      $stderr.puts
      raise NoMethodError, "undefined method `execute' for main:Object"
    end
  end
end
extend Capistrano::DSL
