require_relative '../capistrano_config'

desc 'deploy [STAGE]', 'Guided deployment across branches'
long_desc <<-LONGDESC
Example: `geordi deploy` or `geordi deploy p[roduction]` or `geordi deploy --current-branch`

Merge, push and deploy with a single command! **It always tells what it will do
before it does it.** There are different scenarios where this command is handy:

- *Production deploy:* From the master branch, run `geordi deploy production`.
  This will merge `master` to `production`, push and deploy to production.

- *Feature branch deploy:* From a feature branch, run `geordi deploy staging`.
  This will merge the feature branch to `master`, push and deploy to staging.

  To deploy a feature branch directly without merging, run
  `geordi deploy --current-branch`. This feature depends on the environment
  variable `DEPLOY_BRANCH` to be picked up in the respective deploy file.

- *Simple deploy:* If the source branch matches the target branch, merging will
  be skipped.

Calling the command without arguments will infer the target stage from the
current branch and fall back to master/staging. (Will use the actual main branch
of the repository, e.g. "main" instead of "master".)

Finds available Capistrano stages by their prefix, e.g. `geordi deploy p` will
deploy production, `geordi deploy mak` will deploy a `makandra` stage if there
is a file config/deploy/makandra.rb.

If Linear team ids are configured (see `geordi commit`), will offer to move deployed issues to a new state. Disable this with "skip".

If your project is running Capistrano 2, Geordi will use `cap deploy:migrations` by default. Skip migrations by passing the -M option: `geordi deploy -M staging`.
LONGDESC

option :no_migrations, aliases: '-M', type: :boolean,
  desc: 'Run cap deploy instead of cap deploy:migrations'
option :current_branch, aliases: '-c', type: :boolean,
  desc: 'Set DEPLOY_BRANCH to the current branch during deploy'

def deploy(target_stage = nil)
  require 'geordi/git'
  require 'geordi/linear_client'

  settings = Settings.new
  linear_client = LinearClient.new

  # Set/Infer default values
  branch_stage_map = { 'master' => 'staging', 'main' => 'staging', 'production' => 'production' }
  if target_stage && !Util.deploy_targets.include?(target_stage)
    # Target stage autocompletion from available stages
    target_stage = Util.deploy_targets.find { |t| t.start_with? target_stage }
    target_stage || Interaction.warn('Given deployment stage not found')
  end

  # Retrieve required information ##############################################
  target_stage ||= Interaction.prompt 'Deployment stage:', branch_stage_map.fetch(Git.current_branch, 'staging')
  capistrano_config = CapistranoConfig.new(target_stage)

  if options.current_branch
    stage_file = "config/deploy/#{target_stage}.rb"
    Util.file_containing?(stage_file, 'DEPLOY_BRANCH') || Interaction.fail(<<~ERROR)
      To deploy from the current branch, configure #{stage_file} to respect the
      environment variable DEPLOY_BRANCH. Example:

      set :branch, ENV['DEPLOY_BRANCH'] || 'master'
    ERROR

    source_branch = target_branch = Git.current_branch
  else # Normal deploy
    source_branch = Interaction.prompt 'Source branch:', Git.current_branch

    deploy_branch = capistrano_config.branch
    deploy_branch ||= Git.default_branch
    target_branch = Interaction.prompt 'Deploy branch:', deploy_branch
  end

  if settings.linear_integration_set_up?
    config_state = settings.linear_state_after_deploy(target_stage)
    config_state = 'skip' if config_state.empty?
    target_state = Interaction.prompt("Move deployed Linear issues to state:", config_state)
    target_state = '' if target_state.empty? || target_state == 'skip'
  end

  merge_needed = (source_branch != target_branch)
  push_needed = merge_needed || `git cherry -v | wc -l`.strip.to_i > 0
  linear_issue_ids = []
  if push_needed
    commit_messages = Git.commits_between(source_branch, target_branch)
    linear_issue_ids = LinearClient.extract_issue_ids(commit_messages)
  end
  move_issues = !linear_issue_ids.empty? && target_state && !target_state.empty?

  # Checks #####################################################################
  Interaction.note "Checking whether your #{source_branch} branch is ready"
  Util.run!("git checkout #{source_branch}")
  if (`git status -s | wc -l`.strip != '0') && !Util.testing?
    Interaction.warn "Your #{source_branch} branch holds uncommitted changes."
    Interaction.prompt('Continue anyway?', 'n', /y|yes/) || raise('Cancelled.')
  else
    puts 'All good.'
  end

  if merge_needed
    Interaction.note "Checking what's in your #{target_branch} branch right now"
    Util.run!("git checkout #{target_branch} && git pull")
  end

  # Confirm ####################################################################
  Interaction.announce 'Planned actions'
  Interaction.note "Merge branch #{source_branch} into #{target_branch}" if merge_needed
  if push_needed
    Interaction.note 'Push these commits:'
    Util.run!("git --no-pager log origin/#{target_branch}..#{source_branch} --oneline")
  end
  Interaction.note "Deploy to #{target_stage}"
  Interaction.note "From current branch #{source_branch}" if options.current_branch

  if move_issues
    relevant_commits = linear_client.filter_by_issue_ids(commit_messages, linear_issue_ids)
    Interaction.note("Move these Linear issues to state \"#{target_state}\":")
    puts relevant_commits.join("\n")
  end

  # Execute ####################################################################
  if Interaction.prompt('Execute this now?', 'y', /y|yes/)
    puts
    git_call = []
    git_call << "git merge #{source_branch}" if merge_needed
    git_call << 'git push' if push_needed

    invoke_geordi 'bundle_install'

    capistrano_call = "cap #{target_stage} deploy"
    capistrano_call << ':migrations' unless Util.gem_major_version('capistrano') == 3 || options.no_migrations
    capistrano_call = "bundle exec #{capistrano_call}" if Util.file_containing?('Gemfile', /capistrano/)
    capistrano_call = "DEPLOY_BRANCH=#{source_branch} #{capistrano_call}" if options.current_branch

    if git_call.any?
      Util.run!(git_call.join(' && '), show_cmd: true)
    end

    Util.run!(capistrano_call, show_cmd: true)

    if move_issues
      linear_client.move_issues_to_state(linear_issue_ids, target_state)
      settings.persist_linear_state_after_deploy(target_stage, target_state)
    end

    Interaction.success "Successfully deployed to #{target_stage}."

    Hint.did_you_know [
      :capistrano,
      :security_update,
    ]
  else
    Util.run!("git checkout #{source_branch}")
    Interaction.fail 'Cancelled.'
  end
end
