require "English"
require "fileutils"
require "pathname"
require "open3"

module TestApp
  extend self

  def install
    install_test_app_with(default_config)
  end

  def default_config
    <<-CONFIG
      set :deploy_to, '#{deploy_to}'
      set :repo_url, 'https://github.com/capistrano/capistrano.git'
      set :branch, 'master'
      set :ssh_options, { keys: '#{File.expand_path('../../.docker/ssh_key_rsa', __dir__)}', auth_methods: ['publickey'] }
      server 'deployer@localhost:2022', roles: %w{web app}
      set :linked_files, #{linked_files}
      set :linked_dirs, #{linked_dirs}
      set :format_options, log_file: nil
      set :local_user, #{current_user.inspect}
    CONFIG
  end

  def linked_files
    %w{config/database.yml}
  end

  def linked_file
    shared_path.join(linked_files.first)
  end

  def linked_dirs
    %w{bin log public/system}
  end

  def create_test_app
    FileUtils.rm_rf(test_app_path)
    FileUtils.mkdir(test_app_path)

    File.write(gemfile, <<-GEMFILE.gsub(/^\s+/, ""))
      source "https://rubygems.org"

      gem "capistrano", path: #{path_to_cap.to_s.inspect}
      gem "ed25519", ">= 1.2", "< 2.0"
      gem "bcrypt_pbkdf", ">= 1.0", "< 2.0"
    GEMFILE

    Dir.chdir(test_app_path) do
      run "bundle"
    end
  end

  def install_test_app_with(config)
    create_test_app
    Dir.chdir(test_app_path) do
      run "cap install STAGES=#{stage}"
    end
    write_local_deploy_file(config)
  end

  def write_local_deploy_file(config)
    File.open(test_stage_path, "w") do |file|
      file.write config
    end
  end

  def write_local_stage_file(filename, config=nil)
    File.open(test_app_path.join("config/deploy/#{filename}"), "w") do |file|
      file.write(config) if config
    end
  end

  def append_to_deploy_file(config)
    File.open(test_stage_path, "a") do |file|
      file.write config + "\n"
    end
  end

  def prepend_to_capfile(config)
    current_capfile = File.read(capfile)
    File.open(capfile, "w") do |file|
      file.write config
      file.write current_capfile
    end
  end

  def create_shared_directory(path)
    FileUtils.mkdir_p(shared_path.join(path))
  end

  def create_shared_file(path)
    File.open(shared_path.join(path), "w")
  end

  def cap(task, subdirectory=nil)
    run "cap #{stage} #{task} --trace", subdirectory
  end

  def run(command, subdirectory=nil)
    command = "bundle exec #{command}" unless command =~ /^bundle\b/
    dir = subdirectory ? test_app_path.join(subdirectory) : test_app_path
    output, status = Dir.chdir(dir) do
      with_clean_bundler_env { Open3.capture2e(command) }
    end
    [status.success?, output]
  end

  def stage
    "test"
  end

  def test_stage_path
    test_app_path.join("config/deploy/test.rb")
  end

  def test_app_path
    Pathname.new("/tmp/test_app")
  end

  def deploy_to
    Pathname.new("/home/deployer/var/www/deploy")
  end

  def shared_path
    deploy_to.join("shared")
  end

  def current_path
    deploy_to.join("current")
  end

  def releases_path
    deploy_to.join("releases")
  end

  def release_path(t=timestamp)
    releases_path.join(t)
  end

  def timestamp(offset=0)
    (Time.now.utc + offset).strftime("%Y%m%d%H%M%S")
  end

  def repo_path
    deploy_to.join("repo")
  end

  def path_to_cap
    File.expand_path(".")
  end

  def gemfile
    test_app_path.join("Gemfile")
  end

  def capfile
    test_app_path.join("Capfile")
  end

  def current_user
    "(GitHub Web Flow) via ShipIt"
  end

  def task_dir
    test_app_path.join("lib/capistrano/tasks")
  end

  def copy_task_to_test_app(source)
    FileUtils.cp(source, task_dir)
  end

  def config_path
    test_app_path.join("config")
  end

  def move_configuration_to_custom_location(location)
    prepend_to_capfile(
      <<-CONFIG
        set :stage_config_path, "app/config/deploy"
        set :deploy_config_path, "app/config/deploy.rb"
      CONFIG
    )

    location = test_app_path.join(location)
    FileUtils.mkdir_p(location)
    FileUtils.mv(config_path, location)
  end

  def git_wrapper_path_glob
    "/tmp/git-ssh-*.sh"
  end

  def with_clean_bundler_env(&block)
    return yield unless defined?(Bundler)

    if Bundler.respond_to?(:with_unbundled_env)
      Bundler.with_unbundled_env(&block)
    else
      Bundler.with_clean_env(&block)
    end
  end
end
