def find_rails_path(root_path)
  paths = %w(spec/dummy test/dummy .)
  paths.find { |path| File.exist?(File.expand_path(path, root_path)) }
end

ROOT_PATH = File.expand_path(Dir.pwd)
RAILS_PATH = find_rails_path(ROOT_PATH)
ENV_PATH  = File.expand_path('config/environment',  RAILS_PATH)
BOOT_PATH = File.expand_path('config/boot',  RAILS_PATH)
APP_PATH  = File.expand_path('config/application',  RAILS_PATH) unless defined? APP_PATH

require 'zeus'

def gem_is_bundled?(gem)
  gemfile_lock_contents = File.read(ROOT_PATH + "/Gemfile.lock")
  gemfile_lock_contents.scan(/^\s*#{gem} \(([^=~><]+?)\)/).flatten.first
end

if version = gem_is_bundled?('method_source')
  gem 'method_source', version
end

require 'zeus/m'

module Zeus
  class Rails < Plan
    def after_fork
      reconnect_activerecord
      restart_girl_friday
      reconnect_redis
    end

    def _monkeypatch_rake
      if version = gem_is_bundled?('rake')
        gem 'rake', version
      end
      require 'rake/testtask'
      Rake::TestTask.class_eval {

        # Create the tasks defined by this task lib.
        def define
          desc "Run tests" + (@name==:test ? "" : " for #{@name}")
          task @name do
            # ruby "#{ruby_opts_string} #{run_code} #{file_list_string} #{option_list}"
            rails_env = ENV['RAILS_ENV']
            rubyopt = ENV['RUBYOPT']
            ENV['RAILS_ENV'] = nil
            ENV['RUBYOPT'] = nil # bundler sets this to require bundler :|
            puts "zeus test #{file_list_string}"
            ret = system "zeus test #{file_list_string}"
            ENV['RAILS_ENV'] = rails_env
            ENV['RUBYOPT'] = rubyopt
            ret
          end
          self
        end

        alias_method :_original_define, :define

        def self.inherited(klass)
          return unless klass.name == "TestTaskWithoutDescription"
          klass.class_eval {
            def self.method_added(sym)
              class_eval do
                if !@rails_hack_reversed
                  @rails_hack_reversed = true
                  alias_method :define, :_original_define
                  def desc(*)
                  end
                end
              end
            end
          }
        end
      }
    end

    def boot
      _monkeypatch_rake
      $LOAD_PATH.unshift "./lib"

      require BOOT_PATH
      # config/application.rb normally requires 'rails/all'.
      # Some 'alternative' ORMs such as Mongoid give instructions to switch this require
      # out for a list of railties, not including ActiveRecord.
      # We grep config/application.rb for all requires of rails/all or railties, and require them.
      rails_components = File.read(APP_PATH + ".rb").
        scan(/^\s*require\s*['"](.*railtie.*|rails\/all)['"]/).flatten

      rails_components = ["rails/all"] if rails_components == []
      rails_components.each do |component|
        require component
      end
    end

    def default_bundle
      Bundler.require(:default)
      require 'zeus/pry' if defined?(Pry)
      Zeus::LoadTracking.add_feature('./Gemfile.lock')
    end

    def development_environment
      Bundler.require(:development)
      ::Rails.env = ENV['RAILS_ENV'] = "development"
      require APP_PATH
      ::Rails.application.require_environment!
    end

    def prerake
      require 'rake'
    end

    def rake
      Rake.application.run
    end

    def generate
      load_rails_generators

      if rails_5_1_or_higher?
        run_rails_5_1_or_higher_command('generate')
      else
        require 'rails/commands/generate'
      end

    end

    def destroy
      load_rails_generators
      if rails_5_1_or_higher?
        run_rails_5_1_or_higher_command('destroy')
      else
        require 'rails/commands/destroy'
      end
    end

    def runner
      if rails_5_1_or_higher?
        run_rails_5_1_or_higher_command('runner')
      else
        require 'rails/commands/runner'
      end
    end

    def console
      if rails_5_1_or_higher?
        run_rails_5_1_or_higher_command('console')
      else
        require 'rails/commands/console'
        if defined?(Pry)
          # Adding Rails Console helpers to Pry.
          if (3..4).include?(::Rails::VERSION::MAJOR)
            require 'rails/console/app'
            require 'rails/console/helpers'
            TOPLEVEL_BINDING.eval('self').extend ::Rails::ConsoleMethods
          end

          Pry.start
        else
          ::Rails::Console.start(::Rails.application)
        end
      end
    end

    def dbconsole
      if rails_5_1_or_higher?
        run_rails_5_1_or_higher_command('dbconsole')
      else
        require 'rails/commands/dbconsole'

        meth = ::Rails::DBConsole.method(:start)

        # `Rails::DBConsole.start` has been changed to load faster in
        # https://github.com/rails/rails/commit/346bb018499cde6699fcce6c68dd7e9be45c75e1
        #
        # This will work with both versions.
        if meth.arity.zero?
          ::Rails::DBConsole.start
        else
          ::Rails::DBConsole.start(::Rails.application)
        end
      end
    end

    def server
      if rails_5_1_or_higher?
        run_rails_5_1_or_higher_command('server')
      else
        require 'rails/commands/server'
        server = ::Rails::Server.new
        Dir.chdir(::Rails.application.root)
        server.start
      end
    end

    def test_environment
      Bundler.require(:test)

      ::Rails.env = ENV['RAILS_ENV'] = 'test'
      require APP_PATH

      $rails_rake_task = 'yup' # lie to skip eager loading
      ::Rails.application.require_environment!
      $rails_rake_task = nil

      $LOAD_PATH.unshift ".", "./lib", "./test", "./spec"
    end

    def test_helper
      if ENV['RAILS_TEST_HELPER']
        require ENV['RAILS_TEST_HELPER']
      else
        if File.exist?(ROOT_PATH + "/spec/rails_helper.rb")
          # RSpec >= 3.0+
          require 'rails_helper'
        elsif File.exist?(ROOT_PATH + "/spec/spec_helper.rb")
          # RSpec < 3.0
          require 'spec_helper'
        elsif File.exist?(ROOT_PATH + "/test/minitest_helper.rb")
          require 'minitest_helper'
        else
          require 'test_helper'
        end
      end
    end

    def test(argv=ARGV)
      # if there are two test frameworks and one of them is RSpec,
      # then "zeus test/rspec/testrb" without arguments runs the
      # RSpec suite by default.
      if using_rspec?(argv)
        ARGV.replace(argv)
        # if no directory is given, run the default spec directory
        argv << "spec" if argv.empty?
        if RSpec::Core::Runner.respond_to?(:invoke)
          RSpec::Core::Runner.invoke
        else
          RSpec::Core::Runner.run(argv)
        end
      else
        require 'minitest/autorun' if using_minitest?
        # Minitest and old Test::Unit
        Zeus::M.run(argv)
      end
    end

    private

    def using_rspec?(argv)
      defined?(RSpec) && (argv.empty? || spec_file?(argv))
    end

    def using_minitest?
      defined?(:MiniTest) || defined?(:Minitest)
    end

    SPEC_DIR_REGEXP = %r"(^|/)spec"
    SPEC_FILE_REGEXP = /.+_spec\.rb$/

    def spec_file?(argv)
      argv.any? do |arg|
        arg.match(Regexp.union(SPEC_DIR_REGEXP, SPEC_FILE_REGEXP))
      end
    end

    def restart_girl_friday
      return unless defined?(GirlFriday::WorkQueue)
      # The Actor is run in a thread, and threads don't persist post-fork.
      # We just need to restart each one in the newly-forked process.
      ObjectSpace.each_object(GirlFriday::WorkQueue) do |obj|
        obj.send(:start)
      end
    end

    def reconnect_activerecord
      return unless defined?(ActiveRecord::Base)
      begin
        ActiveRecord::Base.clear_all_connections!
        ActiveRecord::Base.establish_connection
        if ActiveRecord::Base.respond_to?(:shared_connection)
          ActiveRecord::Base.shared_connection = ActiveRecord::Base.retrieve_connection
        end
      rescue ActiveRecord::AdapterNotSpecified
      end
    end

    def reconnect_redis
      return unless defined?(Redis::Client)
      ObjectSpace.each_object(Redis::Client) do |client|
        client.connect rescue nil
      end
    end

    def load_rails_generators
      require 'rails/generators'
      ::Rails.application.load_generators
    rescue LoadError # Rails 3.0 doesn't require this block to be run, but 3.2+ does
    end

    def run_rails_5_1_or_higher_command(command)
      require 'rails/command'
      ::Rails::Command.invoke(command, ARGV)
    end

    def rails_5_1_or_higher?
      (::Rails::VERSION::MAJOR == 5 && ::Rails::VERSION::MINOR >= 1) ||
        ::Rails::VERSION::MAJOR > 5
    end
  end
end

Zeus.plan ||= Zeus::Rails.new
