require 'erb'
require 'forwardable'
require 'honeybadger/cli/main'
require 'pathname'

module Honeybadger
  module CLI
    class Test
      extend Forwardable

      TEST_EXCEPTION = begin
                         exception_name = ENV['EXCEPTION'] || 'HoneybadgerTestingException'
                         Object.const_get(exception_name)
                       rescue
                         Object.const_set(exception_name, Class.new(Exception))
                       end.new('Testing honeybadger via "honeybadger test". If you can see this, it works.')

      class TestBackend
        def initialize(backend)
          @backend = backend
        end

        def self.callings
          @callings ||= Hash.new {|h,k| h[k] = [] }
        end

        def notify(feature, payload)
          response = @backend.notify(feature, payload)
          self.class.callings[feature] << [payload, response]
          response
        end
      end

      def initialize(options)
        @options = options
        @shell = ::Thor::Base.shell.new
      end

      def run
        begin
          require File.join(Dir.pwd, 'config', 'environment.rb')
          raise LoadError unless defined?(::Rails.application)
          say("Detected Rails #{Rails::VERSION::STRING}")
        rescue LoadError
          require 'honeybadger/init/ruby'
        end

        if Honeybadger.config.get(:api_key).to_s =~ BLANK
          say("Unable to send test: Honeybadger API key is missing.", :red)
          exit(1)
        end

        Honeybadger.config.set(:report_data, !options[:dry_run])
        test_backend = TestBackend.new(Honeybadger.config.backend)
        Honeybadger.config.backend = test_backend

        at_exit do
          # Exceptions will already be reported when exiting.
          verify_test unless $!
        end

        run_test
      end

      private

      attr_reader :options

      def_delegator :@shell, :say

      def run_test
        if defined?(::Rails.application) && ::Rails.application
          run_rails_test
        else
          run_standalone_test
        end
      end

      def test_exception_class
        exception_name = ENV['EXCEPTION'] || 'HoneybadgerTestingException'
        Object.const_get(exception_name)
      rescue
        Object.const_set(exception_name, Class.new(Exception))
      end

      def run_standalone_test
        Honeybadger.notify(TEST_EXCEPTION)
      end

      def run_rails_test
        # Suppress error logging in Rails' exception handling middleware. Rails 3.0
        # uses ActionDispatch::ShowExceptions to rescue/show exceptions, but does
        # not log anything but application trace. Rails 3.2 now falls back to
        # logging the framework trace (moved to ActionDispatch::DebugExceptions),
        # which caused cluttered output while running the test task.
        defined?(::ActionDispatch::DebugExceptions) and
          ::ActionDispatch::DebugExceptions.class_eval { def logger(*args) ; @logger ||= Logger.new(nil) ; end }
        defined?(::ActionDispatch::ShowExceptions) and
          ::ActionDispatch::ShowExceptions.class_eval { def logger(*args) ; @logger ||= Logger.new(nil) ; end }

        # Detect and disable the better_errors gem
        if defined?(::BetterErrors::Middleware)
          say('Better Errors detected: temporarily disabling middleware.', :yellow)
          ::BetterErrors::Middleware.class_eval { def call(env) @app.call(env); end }
        end

        begin
          require './app/controllers/application_controller'
        rescue LoadError
          nil
        end

        unless defined?(::ApplicationController)
          say('Error: No ApplicationController found.', :red)
          return false
        end

        eval(<<-CONTROLLER)
        class Honeybadger::TestController < ApplicationController
          # This is to bypass any filters that may prevent access to the action.
          if respond_to?(:prepend_before_action)
            prepend_before_action :test_honeybadger
          else
            prepend_before_filter :test_honeybadger
          end
          def test_honeybadger
            puts "Raising '#{Honeybadger::CLI::Test::TEST_EXCEPTION.class.name}' to simulate application failure."
            raise Honeybadger::CLI::Test::TEST_EXCEPTION
          end
          # Ensure we actually have an action to go to.
          def verify; end
        end
        CONTROLLER

        ::Rails.application.routes.tap do |r|
          # RouteSet#disable_clear_and_finalize prevents existing routes from
          # being cleared. We'll set it back to the original value when we're
          # done so not to mess with Rails state.
          d = r.disable_clear_and_finalize
          begin
            r.disable_clear_and_finalize = true
            r.clear!
            r.draw do
              match 'verify' => 'honeybadger/test#verify', :as => "verify_#{SecureRandom.hex}", :via => :get
            end
            ::Rails.application.routes_reloader.paths.each{ |path| load(path) }
            ::ActiveSupport.on_load(:action_controller) { r.finalize! }
          ensure
            r.disable_clear_and_finalize = d
          end
        end

        ssl = defined?(::Rails.configuration.force_ssl) && ::Rails.configuration.force_ssl
        env = ::Rack::MockRequest.env_for("http#{ ssl ? 's' : nil }://www.example.com/verify", 'REMOTE_ADDR' => '127.0.0.1')

        ::Rails.application.call(env)
      end

      def verify_test
        Honeybadger.flush

        if calling = TestBackend.callings[:notices].find {|c| c[0].exception.eql?(TEST_EXCEPTION) }
          notice, response = *calling

          if !response.success?
            host = Honeybadger.config.get(:'connection.host')
            say(<<-MSG, :red)
!! --- Honeybadger test failed ------------------------------------------------ !!

The error notifier is installed, but we encountered an error:

  #{response.error_message}

To fix this issue, please try the following:

  - Make sure the gem is configured properly.
  - Retry executing this command a few times.
  - Make sure you can connect to #{host} (`ping #{host}`).
  - Email support@honeybadger.io for help. Include as much debug info as you
    can for a faster resolution!

!! --- End -------------------------------------------------------------------- !!
MSG
            exit(1)
          end

          say(generate_success_message(response), :green)

          exit(0)
        end

        say(<<-MSG, :red)
!! --- Honeybadger test failed ------------------------------------------------ !!

Error: The test exception was not reported; the application may not be
configured properly.

This is usually caused by one of the following issues:

  - There was a problem loading your application. Check your logs to see if a
    different exception is being raised.
  - The exception is being rescued before it reaches our Rack middleware. If
    you're using `rescue` or `rescue_from` you may need to notify Honeybadger
    manually: `Honeybadger.notify(exception)`.
  - The honeybadger gem is misconfigured. Check the settings in your
    honeybadger.yml file.
MSG

        notices = TestBackend.callings[:notices].map(&:first)
        unless notices.empty?
          say("\nThe following errors were reported:", :red)
          notices.each {|n| say("\n  - #{n.error_class}: #{n.error_message}", :red) }
        end

        say("\nSee https://docs.honeybadger.io/gem-troubleshooting for more troubleshooting help.\n\n", :red)
        say("!! --- End -------------------------------------------------------------------- !!", :red)

        exit(1)
      end

      def generate_success_message(response)
        notice_id = JSON.parse(response.body)['id']
        notice_url = "https://app.honeybadger.io/notice/#{notice_id}"

        unless options[:install]
          return "⚡ Success: #{notice_url}"
        end

        <<-MSG
⚡ --- Honeybadger is installed! -----------------------------------------------

Good news: You're one deploy away from seeing all of your exceptions in
Honeybadger. For now, we've generated a test exception for you:

  #{notice_url}

Optional steps:

  - Show a feedback form on your error page:
    https://docs.honeybadger.io/gem-feedback
  - Show a UUID or link to Honeybadger on your error page:
    https://docs.honeybadger.io/gem-informer
  - Track deployments (if you're using Capistrano, we already did this):
    https://docs.honeybadger.io/gem-deploys

If you ever need help:

  - Read the gem troubleshooting guide: https://docs.honeybadger.io/gem-troubleshooting
  - Check out our documentation: https://docs.honeybadger.io/
  - Email the founders: support@honeybadger.io

Most people don't realize that Honeybadger is a small, bootstrapped company. We
really couldn't do this without you. Thank you for allowing us to do what we
love: making developers awesome.

Happy 'badgering!

Sincerely,
Ben, Josh and Starr
https://www.honeybadger.io/about/

⚡ --- End --------------------------------------------------------------------
MSG
      end
    end
  end
end