module CapistranoGitFlow
  module Helper


    def gitflow_stage
      original_stage = fetch(:stage)
      original_stage.to_s.include?(":") ? original_stage.split(':').reverse[0] : original_stage
    end

    def gitflow_using_cap3?
      defined?(Capistrano::VERSION) && Capistrano::VERSION.to_s.split('.').first.to_i >= 3
    end

    def gitflow_callbacks
      if gitflow_using_cap3?
        before "deploy", "gitflow:verify_up_to_date"
      else
        before "deploy:update_code", "gitflow:verify_up_to_date"
      end
      after "gitflow:verify_up_to_date", "gitflow:calculate_tag"
    end

    def gitflow_find_task(name)
      defined?(::Rake) ? ::Rake::Task[name] : exists?(name)
    rescue
      nil
    end

    def gitflow_execute_task(name)
      defined?(::Rake) ?  gitflow_find_task(name).invoke : find_and_execute_task(name)
    end

    def gitflow_capistrano_tag
     defined?(capistrano_configuration) ?  capistrano_configuration[:tag] : ENV['TAG']
    end

    def gitflow_last_tag_matching(pattern)
      # search for most recent (chronologically) tag matching the passed pattern, then get the name of that tag.
      last_tag = `git describe --exact-match  --tags --match='#{pattern}' $(git log --tags='#{pattern}*' -n1 --pretty='%h')`.chomp
      last_tag == '' ? nil : last_tag
    end

    def gitflow_last_staging_tag
      gitflow_last_tag_matching('staging-*')
    end

    def gitflow_ask_confirm(message)
      if gitflow_using_cap3?
        $stdout.print "#{message}"
        $stdin.gets.to_s.chomp
      else
        Capistrano::CLI.ui.ask("#{message}")
      end
    end

    def gitflow_next_staging_tag
      hwhen  = Date.today.to_s
      who = `whoami`.chomp.to_url
      what = ENV['TAG_NAME'] ? ENV['TAG_NAME'] : gitflow_ask_confirm("What does this release introduce? (this will be normalized and used in the tag for this release) ")

      abort "No tag has been provided: #{what.inspect}" if what == ''

      last_staging_tag = gitflow_last_tag_matching("staging-#{hwhen}-*")
      new_tag_serial = if last_staging_tag && last_staging_tag =~ /staging-[0-9]{4}-[0-9]{2}-[0-9]{2}\-([0-9]*)/
        $1.to_i + 1
      else
        1
      end

      "#{gitflow_stage}-#{hwhen}-#{new_tag_serial}-#{who}-#{what.to_url}"
    end

    def gitflow_last_production_tag()
      gitflow_last_tag_matching('production-*')
    end

    def gitflow_using_git?
      fetch(:scm, :git).to_sym == :git
    end


    def gitflow_verify_up_to_date
      if gitflow_using_git?
        set :local_branch, `git branch --no-color 2> /dev/null | sed -e '/^[^*]/d'`.gsub(/\* /, '').chomp
        set :local_sha, `git log --pretty=format:%H HEAD -1`.chomp
        set :origin_sha, `git log --pretty=format:%H #{fetch(:local_branch)} -1`
        unless fetch(:local_sha) == fetch(:origin_sha)
          abort """
Your #{fetch(:local_branch)} branch is not up to date with origin/#{fetch(:local_branch)}.
Please make sure you have pulled and pushed all code before deploying:

git pull origin #{fetch(:local_branch)}
# run tests, etc
git push origin #{fetch(:local_branch)}

          """
        end
      end
    end



    def gitflow_calculate_tag
      if gitflow_using_git?
        # make sure we have any other deployment tags that have been pushed by others so our auto-increment code doesn't create conflicting tags
        `git fetch`
        rake_task_name = "gitflow:tag_#{gitflow_stage}"
        task_exists = gitflow_find_task(rake_task_name)
        if !task_exists.nil? && task_exists!= false

            gitflow_execute_task(rake_task_name)

          system "git push --tags origin #{fetch(:local_branch)}"
          if $? != 0
            abort "git push failed"
          end
        else
          puts "Will deploy tag: #{fetch(:local_branch)}"
          set :branch, fetch(:local_branch)
        end
      end
    end

    def gitflow_commit_log
      from_tag = if gitflow_stage.to_s == 'production'
        gitflow_last_production_tag
      elsif gitflow_stage.to_s == 'staging'
        gitflow_last_staging_tag
      else
        abort "Unsupported stage #{gitflow_stage}"
      end

      # no idea how to properly test for an optional cap argument a la '-s tag=x'
      to_tag = gitflow_capistrano_tag
      to_tag ||= begin
        puts "Calculating 'end' tag for :commit_log for '#{gitflow_stage}'"
        to_tag = if  gitflow_stage.to_s == 'production'
          gitflow_last_staging_tag
        elsif gitflow_stage.to_s == 'staging'
          'master'
        else
          abort "Unsupported stage #{gitflow_stage}"
        end
      end



      # use custom compare command if set
      if ENV['git_log_command'] && ENV['git_log_command'].strip != ''
        command = "git #{ENV['git_log_command']} #{from_tag}..#{to_tag}"
      else
        # default compare command
        # be awesome for github
        if `git config remote.origin.url` =~ /git@github.com:(.*)\/(.*).git/
          command = "open https://github.com/#{$1}/#{$2}/compare/#{from_tag}...#{to_tag}"
        else
          command = "git log #{from_tag}..#{to_tag}"
        end
      end
      puts "Displaying commits from #{from_tag} to #{to_tag} via:\n#{command}"
      system command

      puts ""
    end


    def gitflow_tag_staging
      current_sha = `git log --pretty=format:%H HEAD -1`
      last_staging_tag_sha = if gitflow_last_staging_tag
        `git log --pretty=format:%H #{gitflow_last_staging_tag} -1`
      end

      if last_staging_tag_sha == current_sha
        puts "Not re-tagging staging because latest tag (#{gitflow_last_staging_tag}) already points to HEAD"
        new_staging_tag = gitflow_last_staging_tag
      else
        new_staging_tag = gitflow_next_staging_tag
        puts "Tagging current branch for deployment to staging as '#{new_staging_tag}'"
        system "git tag -a -m 'tagging current code for deployment to staging' #{new_staging_tag}"
      end

      set :branch, new_staging_tag
    end


    def gitflow_tag_production
      promote_to_production_tag = gitflow_capistrano_tag || gitflow_last_staging_tag

      unless promote_to_production_tag && promote_to_production_tag =~ /staging-.*/
        abort "Couldn't find a staging tag to deploy; use '-s tag=staging-YYYY-MM-DD.X'"
      end
      unless gitflow_last_tag_matching(promote_to_production_tag)
        abort "Staging tag #{promote_to_production_tag} does not exist."
      end

      promote_to_production_tag =~ /^staging-(.*)$/
      new_production_tag = "production-#{$1}"

      if new_production_tag == gitflow_last_production_tag
        puts "Not re-tagging #{gitflow_last_production_tag} because it already exists"
        really_deploy = gitflow_ask_confirm("Do you really want to deploy #{gitflow_last_production_tag}? [y/N]")

        exit(1) unless really_deploy.to_url =~ /^[Yy]$/
      else
        puts "Preparing to promote staging tag '#{promote_to_production_tag}' to '#{new_production_tag}'"
        gitflow_commit_log
        unless gitflow_capistrano_tag
          really_deploy = gitflow_ask_confirm("Do you really want to deploy #{new_production_tag}? [y/N]")

          exit(1) unless really_deploy.to_url =~ /^[Yy]$/
        end
        puts "Promoting staging tag #{promote_to_production_tag} to production as '#{new_production_tag}'"
        system "git tag -a -m 'tagging current code for deployment to production' #{new_production_tag} #{promote_to_production_tag}"
      end

      set :branch, new_production_tag
    end

  end
end