module Bard; end

require "bard/base"
require "bard/git"
require "bard/ci"
require "bard/data"
require "bard/github"
require "bard/ping"
require "bard/config"

class Bard::CLI < Thor
  include Thor::Actions

  def initialize(*args, **kwargs, &block)
    super
    @config = Bard::Config.new(project_name, path: "bard.rb")
  end

  desc "data --from=production --to=local", "copy database and assets from from to to"
  method_options %w[from] => :string, %w[to] => :string
  def data
    default_from = @config.servers.key?(:production) ? "production" : "staging"
    from = options.fetch(:from, default_from)
    to = options.fetch(:to, "local")
    Data.new(self, from, to).call
  end

  method_options %w( verbose -v ) => :boolean
  desc "stage [BRANCH=HEAD]", "pushes current branch, and stages it"
  def stage branch=Git.current_branch
    unless @config.servers.key?(:production)
      raise Thor::Error.new("`bard stage` is disabled until a production server is defined. Until then, please use `bard deploy` to deploy to the staging server.")
    end

    run_crucial "git push -u origin #{branch}", verbose: true
    command = "git fetch && git checkout -f origin/#{branch} && bin/setup"
    run_crucial ssh_command(:staging, command)
    puts green("Stage Succeeded")

    ping :staging
  end

  method_options %w[verbose -v] => :boolean, %w[skip-ci] => :boolean, %w[local-ci -l] => :boolean
  desc "deploy [TO=production]", "checks that current branch is a ff with master, checks with ci, merges into master, deploys to target, and then deletes branch."
  def deploy to=nil
    branch = Git.current_branch

    if branch == "master"
      run_crucial "git push origin #{branch}:#{branch}" if !Git.up_to_date_with_remote?(branch)
      invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]

    else
      run_crucial "git fetch origin master:master"

      unless Git.fast_forward_merge?("origin/master", branch)
        puts "The master branch has advanced. Attempting rebase..."
        run_crucial "git rebase origin/master"
      end

      run_crucial "git push -f origin #{branch}:#{branch}"

      invoke :ci, [branch], options.slice("local-ci") unless options["skip-ci"]

      run_crucial "git push origin #{branch}:master"
      run_crucial "git fetch origin master:master"
    end

    if `git remote` =~ /\bgithub\b/
      run_crucial "git push github"
    end

    to ||= @config.servers.key?(:production) ? :production : :staging

    command = "git pull origin master && bin/setup"
    run_crucial ssh_command(to, command)

    puts green("Deploy Succeeded")

    if branch != "master"
      puts "Deleting branch: #{branch}"
      run_crucial "git push --delete origin #{branch}"

      if branch == Git.current_branch
        run_crucial "git checkout master"
      end

      run_crucial "git branch -D #{branch}"
    end

    ping to
  end

  method_options %w[verbose -v] => :boolean, %w[local-ci -l] => :boolean, %w[status -s] => :boolean
  desc "ci [BRANCH=HEAD]", "runs ci against BRANCH"
  def ci branch=Git.current_branch
    ci = CI.new(project_name, branch, local: options["local-ci"])
    if ci.exists?
      return puts ci.status if options["status"]

      puts "Continuous integration: starting build on #{branch}..."

      success = ci.run do |elapsed_time, last_time|
        if last_time
          percentage = (elapsed_time.to_f / last_time.to_f * 100).to_i
          output = "  Estimated completion: #{percentage}%"
        else
          output = "  No estimated completion time. Elapsed time: #{elapsed_time} sec"
        end
        print "\x08" * output.length
        print output
        $stdout.flush
      end

      if success
        puts
        puts "Continuous integration: success!"
        if ci.jenkins? && File.exist?("coverage")
          puts "Downloading test coverage from CI..."
          download_ci_test_coverage
        end
        puts "Deploying..."
      else
        puts
        puts ci.last_response
        puts ci.console
        puts red("Automated tests failed!")
        exit 1
      end

    else
      puts red("No CI found for #{project_name}!")
      puts "Re-run with --skip-ci to bypass CI, if you absolutely must, and know what you're doing."
      exit 1
    end
  end

  desc "open [SERVER=production]", "opens the url in the web browser."
  def open server=nil
    server ||= @config.servers.key?(:production) ? :production : :staging
    server = @config.servers[server.to_sym]
    exec "xdg-open #{server.default_ping}"
  end

  desc "hurt", "reruns a command until it fails"
  def hurt *args
    1.upto(Float::INFINITY) do |count|
      puts "Running attempt #{count}"
      system *args
      unless $?.success?
        puts "Ran #{count-1} times before failing"
        break
      end
    end
  end

  method_options %w[home] => :boolean
  desc "ssh [TO=production]", "logs into the specified server via SSH"
  def ssh to=:production
    command = "exec $SHELL -l"
    if to == "theia" && !options["home"]
      server = @config.servers[:theia]
      command = %(bash -lic "exec ./vagrant \\"cd #{server.path} && #{command}\\"")
      exec ssh_command(to, command, home: true)
    else
      exec ssh_command(to, command, home: options["home"])
    end
  end

  desc "install", "copies bin/setup and bin/ci scripts into current project."
  def install
    install_files_path = File.expand_path(File.join(__dir__, "../install_files/*"))
    system "cp -R #{install_files_path} bin/"
    github_files_path = File.expand_path(File.join(__dir__, "../install_files/.github"))
    system "cp -R #{github_files_path} ./"
  end

  desc "setup", "installs app in nginx"
  def setup
    path = "/etc/nginx/sites-available/#{project_name}"
    dest_path = path.sub("sites-available", "sites-enabled")
    server_name = "#{project_name}.localhost"

    create_file path, <<~NGINX
      server {
        listen 80;
        server_name #{server_name};

        root #{Dir.pwd}/public;
        passenger_enabled on;

        location ~* \\.(ico|css|js|gif|jp?g|png|webp) {
          access_log off;
          if ($request_filename ~ "-[0-9a-f]{32}\\.") {
            expires max;
            add_header Cache-Control public;
          }
        }
        gzip_static on;
      }
    NGINX

    FileUtils.ln_sf(path, dest_path) if !File.exist?(dest_path)
    run "service nginx restart"
  rescue Errno::EACCES
    raise InvocationError.new("please re-run with sudo")
  end

  desc "ping [SERVER=production]", "hits the server over http to verify that its up."
  def ping server=:production
    server = @config.servers[server.to_sym]
    unless Bard::Ping.call(server)
      puts "#{server.key.to_s.capitalize} is down!"
      exit 1
    end
  end

  desc "master_key --from=production --to=local", "copy master key from from to to"
  method_options %w[from] => :string, %w[to] => :string
  def master_key
    default_from = @config.servers.key?(:production) ? "production" : "staging"
    from = options.fetch(:from, default_from)
    to = options.fetch(:to, "local")
    if to == "local"
      copy :from, from, "config/master.key"
    end
    if from == "local"
      copy :to, to, "config/master.key"
    end
  end

  desc "download_ci_test_coverage", "download latest test coverage information from CI"
  def download_ci_test_coverage
    rsync :from, :ci, "coverage"
  end

  desc "vim", "open all files that have changed since master"
  def vim branch="master"
    exec "vim -p `git diff #{branch} --name-only | grep -v sass$ | tac`"
  end

  def self.exit_on_failure? = true
end