require "honeybadger/cli/deploy"
require "honeybadger/cli/exec"
require "honeybadger/cli/heroku"
require "honeybadger/cli/install"
require "honeybadger/cli/notify"
require "honeybadger/cli/test"
require "honeybadger/config"
require "honeybadger/config/defaults"
require "honeybadger/ruby"
require "honeybadger/util/http"
require "honeybadger/version"
require "logger"

module Honeybadger
  module CLI
    BLANK = /\A\s*\z/

    NOTIFIER = {
      name: "honeybadger-ruby (cli)".freeze,
      url: "https://github.com/honeybadger-io/honeybadger-ruby".freeze,
      version: VERSION,
      language: nil
    }.freeze

    class Main < Thor
      def self.project_options
        option :api_key, required: false, aliases: :"-k", type: :string, desc: "Api key of your Honeybadger application"
        option :environment, required: false, aliases: [:"-e", :"-env"], type: :string, desc: 'Environment this command is being executed in (i.e. "production", "staging")'
        option :skip_rails_load, required: false, type: :boolean, desc: "Flag to skip rails initialization"
      end

      def help(*args, &block)
        if args.size == 0
          say(<<~WELCOME)
            ⚡  Honeybadger v#{VERSION}
            
            Honeybadger is your favorite error tracker for Ruby. When your app raises an
            exception we notify you with all the context you need to fix it.
            
            The Honeybadger CLI provides tools for interacting with Honeybadger via the
            command line.
            
            If you need support, please drop us a line: support@honeybadger.io
            
          WELCOME
        end
        super
      end

      desc "install API_KEY", "Install Honeybadger into a new project"
      option :insights, type: :boolean, aliases: :"-i", default: true, desc: "Enable Honeybadger Insights"
      option :host, type: :string
      option :ui_host, type: :string
      def install(api_key)
        Install.new(options, api_key).run
      rescue => e
        log_error(e)
        exit(1)
      end

      desc "test", "Send a test notification from Honeybadger"
      option :dry_run, type: :boolean, aliases: :"-d", default: false, desc: "Skip sending data to Honeybadger"
      option :file, type: :string, aliases: :"-f", default: nil, desc: "Write the output to FILE"
      def test
        Test.new(options).run
      rescue => e
        log_error(e)
        exit(1)
      end

      desc "deploy", "Notify Honeybadger of deployment"
      project_options
      option :repository, required: true, type: :string, aliases: :"-r", desc: "The address of your repository"
      option :revision, required: true, type: :string, aliases: :"-s", desc: "The revision/sha that is being deployed"
      option :user, required: true, type: :string, aliases: :"-u", default: ENV["USER"] || ENV["USERNAME"], desc: "The local user who is deploying"
      def deploy
        config = build_config(options)

        if BLANK.match?(config.get(:api_key).to_s)
          say("No value provided for required options '--api-key'")
          exit(1)
        end

        Deploy.new(options, [], config).run
      rescue => e
        log_error(e)
        exit(1)
      end

      desc "notify", "Notify Honeybadger of an error"
      project_options
      option :class, required: true, type: :string, aliases: :"-c", default: "CLI Notification", desc: "The class name of the error. (Default: CLI Notification)"
      option :message, required: true, type: :string, aliases: :"-m", desc: "The error message."
      option :action, required: false, type: :string, aliases: :"-a", desc: "The action."
      option :component, required: false, type: :string, aliases: :"-C", desc: "The component."
      option :fingerprint, required: false, type: :string, aliases: :"-f", desc: "The fingerprint."
      option :tags, required: false, type: :string, aliases: :"-t", desc: "The tags."
      option :url, required: false, type: :string, aliases: :"-u", desc: "The URL."
      def notify
        config = build_config(options)

        if BLANK.match?(config.get(:api_key).to_s)
          say("No value provided for required options '--api-key'")
          exit(1)
        end

        Notify.new(options, [], config).run
      rescue => e
        log_error(e)
        exit(1)
      end

      desc "exec", "Execute a command. If the exit status is not 0, report the result to Honeybadger"
      project_options
      option :quiet, required: false, type: :boolean, aliases: :"-q", default: false, desc: "Suppress all output unless notification fails."
      def exec(*args)
        if args.size == 0
          say("honeybadger: exec needs a command to run", :red)
          exit(1)
        end

        config = build_config(options)

        if BLANK.match?(config.get(:api_key).to_s)
          say("No value provided for required options '--api-key'", :red)
          exit(1)
        end

        Exec.new(options, args, config).run
      rescue => e
        log_error(e)
        exit(1)
      end

      desc "heroku SUBCOMMAND ...ARGS", "Manage Honeybadger on Heroku"
      subcommand "heroku", Heroku

      private

      def fetch_value(options, key)
        (options[key] == key) ? nil : options[key]
      end

      def build_config(options)
        load_env(options)

        config = Honeybadger.config
        config.set(:report_data, true)
        config.set(:api_key, fetch_value(options, "api_key")) if options.has_key?("api_key")
        config.set(:env, fetch_value(options, "environment")) if options.has_key?("environment")

        config
      end

      def load_env(options)
        # Initialize Rails when running from Rails root.
        environment_rb = File.join(Dir.pwd, "config", "environment.rb")
        if File.exist?(environment_rb)
          load_rails_env_if_allowed(environment_rb, options)
        end
        # Ensure config is loaded (will be skipped if initialized by Rails).
        Honeybadger.config.load!
      end

      def load_rails_env_if_allowed(environment_rb, options)
        # Skip Rails initialization according to option flag
        if options.has_key?("skip_rails_load") && fetch_value(options, "skip_rails_load")
          say("Skipping Rails initialization.")
        else
          load_rails_env(environment_rb)
        end
      end

      def load_rails_env(environment_rb)
        begin
          require "rails"
        rescue LoadError
          # No Rails, so skip loading Rails environment.
          return
        end
        require environment_rb
      end

      def log_error(e)
        case e
        when *Util::HTTP::ERRORS
          say(<<~MSG, :red)
            !! --- Failed to notify Honeybadger ------------------------------------------- !!
            
            # What happened?
            
              We encountered an HTTP error while contacting our service. Issues like this are
              usually temporary.
            
            # Error details
            
              #{e.class}: #{e.message}\n    at #{e.backtrace && e.backtrace.first}
            
            # What can I do?
            
              - Retry the command.
              - Make sure you can connect to api.honeybadger.io (`curl https://api.honeybadger.io/v1/notices`).
              - If you continue to see this message, email us at support@honeybadger.io
                (don't forget to attach this output!)
            
            !! --- End -------------------------------------------------------------------- !!
          MSG
        else
          say(<<~MSG, :red)
            !! --- Honeybadger command failed --------------------------------------------- !!
            
            # What did you try to do?
            
              You tried to execute the following command:
              `honeybadger #{ARGV.join(" ")}`
            
            # What actually happend?
            
              We encountered a Ruby exception and were forced to cancel your request.
            
            # Error details
            
              #{e.class}: #{e.message}
                #{e.backtrace && e.backtrace.join("\n    ")}
            
            # What can I do?
            
              - If you're calling the `install` or `test` command in a Rails app, make sure
                you can boot the Rails console: `bundle exec rails console`.
              - Retry the command.
              - If you continue to see this message, email us at support@honeybadger.io
                (don't forget to attach this output!)
            
            !! --- End -------------------------------------------------------------------- !!
          MSG
        end
      end
    end
  end
end
