namespace :deploy do
  task :starting do
    invoke "deploy:print_config_variables" if fetch(:print_config_variables, false)
    invoke "deploy:check"
    invoke "deploy:set_previous_revision"
    invoke "deploy:set_previous_revision_time"
  end

  task :print_config_variables do
    puts
    puts "------- Printing current config variables -------"
    env.keys.each do |config_variable_key|
      if is_question?(config_variable_key)
        puts "#{config_variable_key.inspect} => Question (awaits user input on next fetch(#{config_variable_key.inspect}))"
      else
        puts "#{config_variable_key.inspect} => #{fetch(config_variable_key).inspect}"
      end
    end

    puts
    puts "------- Printing current config variables of SSHKit mechanism -------"
    puts env.backend.config.inspect
    # puts env.backend.config.backend.config.ssh_options.inspect
    # puts env.backend.config.command_map.defaults.inspect

    puts
  end

  task updating: :new_release_path do
    invoke "deploy:set_current_revision"
    invoke "deploy:set_current_revision_time"
    invoke "deploy:symlink:shared"
  end

  task :reverting do
    invoke "deploy:revert_release"
  end

  task :publishing do
    invoke "deploy:symlink:release"
  end

  task :finishing do
    invoke "deploy:cleanup"
  end

  task :finishing_rollback do
    invoke "deploy:cleanup_rollback"
  end

  task :finished do
    invoke "deploy:log_revision"
  end

  desc "Check required files and directories exist"
  task :check do
    invoke "deploy:check:directories"
    invoke "deploy:check:linked_dirs"
    invoke "deploy:check:make_linked_dirs"
    invoke "deploy:check:linked_files"
  end

  namespace :check do
    desc "Check shared and release directories exist"
    task :directories do
      on release_roles :all do
        execute :mkdir, "-p", shared_path, releases_path
      end
    end

    desc "Check directories to be linked exist in shared"
    task :linked_dirs do
      next unless any? :linked_dirs
      on release_roles :all do
        execute :mkdir, "-p", linked_dirs(shared_path)
      end
    end

    desc "Check directories of files to be linked exist in shared"
    task :make_linked_dirs do
      next unless any? :linked_files
      on release_roles :all do |_host|
        execute :mkdir, "-p", linked_file_dirs(shared_path)
      end
    end

    desc "Check files to be linked exist in shared"
    task :linked_files do
      next unless any? :linked_files
      on release_roles :all do |host|
        linked_files(shared_path).each do |file|
          unless test "[ -f #{file} ]"
            error t(:linked_file_does_not_exist, file: file, host: host)
            exit 1
          end
        end
      end
    end
  end

  namespace :symlink do
    desc "Symlink release to current"
    task :release do
      on release_roles :all do
        tmp_current_path = release_path.parent.join(current_path.basename)
        execute :ln, "-s", release_path, tmp_current_path
        execute :mv, tmp_current_path, current_path.parent
      end
    end

    desc "Symlink files and directories from shared to release"
    task :shared do
      invoke "deploy:symlink:linked_files"
      invoke "deploy:symlink:linked_dirs"
    end

    desc "Symlink linked directories"
    task :linked_dirs do
      next unless any? :linked_dirs
      on release_roles :all do
        execute :mkdir, "-p", linked_dir_parents(release_path)

        fetch(:linked_dirs).each do |dir|
          target = release_path.join(dir)
          source = shared_path.join(dir)
          next if test "[ -L #{target} ]"
          execute :rm, "-rf", target if test "[ -d #{target} ]"
          execute :ln, "-s", source, target
        end
      end
    end

    desc "Symlink linked files"
    task :linked_files do
      next unless any? :linked_files
      on release_roles :all do
        execute :mkdir, "-p", linked_file_dirs(release_path)

        fetch(:linked_files).each do |file|
          target = release_path.join(file)
          source = shared_path.join(file)
          next if test "[ -L #{target} ]"
          execute :rm, target if test "[ -f #{target} ]"
          execute :ln, "-s", source, target
        end
      end
    end
  end

  desc "Clean up old releases"
  task :cleanup do
    on release_roles :all do |host|
      releases = capture(:ls, "-x", releases_path).split
      valid, invalid = releases.partition { |e| /^\d{14}$/ =~ e }

      warn t(:skip_cleanup, host: host.to_s) if invalid.any?

      if valid.count >= fetch(:keep_releases)
        info t(:keeping_releases, host: host.to_s, keep_releases: fetch(:keep_releases), releases: valid.count)
        directories = (valid - valid.last(fetch(:keep_releases))).map do |release|
          releases_path.join(release).to_s
        end
        if test("[ -d #{current_path} ]")
          current_release = capture(:readlink, current_path).to_s
          if directories.include?(current_release)
            warn t(:wont_delete_current_release, host: host.to_s)
            directories.delete(current_release)
          end
        else
          debug t(:no_current_release, host: host.to_s)
        end
        if directories.any?
          directories.each_slice(100) do |directories_batch|
            execute :rm, "-rf", *directories_batch
          end
        else
          info t(:no_old_releases, host: host.to_s, keep_releases: fetch(:keep_releases))
        end
      end
    end
  end

  desc "Remove and archive rolled-back release."
  task :cleanup_rollback do
    on release_roles(:all) do
      last_release = capture(:ls, "-xt", releases_path).split.first
      last_release_path = releases_path.join(last_release)
      if test "[ `readlink #{current_path}` != #{last_release_path} ]"
        execute :tar, "-czf",
                deploy_path.join("rolled-back-release-#{last_release}.tar.gz"),
                last_release_path
        execute :rm, "-rf", last_release_path
      else
        debug "Last release is the current release, skip cleanup_rollback."
      end
    end
  end

  desc "Log details of the deploy"
  task :log_revision do
    on release_roles(:all) do
      within releases_path do
        execute :echo, %Q{"#{revision_log_message}" >> #{revision_log}}
      end
    end
  end

  desc "Revert to previous release timestamp"
  task revert_release: :rollback_release_path do
    on release_roles(:all) do
      set(:revision_log_message, rollback_log_message)
    end
  end

  task :new_release_path do
    set_release_path
  end

  task :rollback_release_path do
    on release_roles(:all) do
      releases = capture(:ls, "-xt", releases_path).split
      if releases.count < 2
        error t(:cannot_rollback)
        exit 1
      end

      rollback_release = ENV["ROLLBACK_RELEASE"]
      index = rollback_release.nil? ? 1 : releases.index(rollback_release)
      if index.nil?
        error t(:cannot_found_rollback_release, release: rollback_release)
        exit 1
      end

      last_release = releases[index]
      set_release_path(last_release)
      set(:rollback_timestamp, last_release)
    end
  end

  desc "Place a REVISION file with the current revision SHA in the current release path"
  task :set_current_revision do
    on release_roles(:all) do
      within release_path do
        execute :echo, "\"#{fetch(:current_revision)}\" > REVISION"
      end
    end
  end

  task :set_previous_revision do
    on release_roles(:all) do
      target = release_path.join("REVISION")
      if test "[ -f #{target} ]"
        set(:previous_revision, capture(:cat, target, "2>/dev/null"))
      end
    end
  end

  desc "Place a REVISION_TIME file with the current revision commit time in the current release path"
  task :set_current_revision_time do
    on release_roles(:all) do
      within release_path do
        if fetch(:current_revision_time)
          execute :echo, "\"#{fetch(:current_revision_time)}\" > REVISION_TIME"
        end
      end
    end
  end

  task :set_previous_revision_time do
    on release_roles(:all) do
      target = release_path.join("REVISION_TIME")
      if test "[ -f #{target} ]"
        set(:previous_revision_time, capture(:cat, target, "2>/dev/null"))
      end
    end
  end

  task :restart
  task :failed
end
