module Micronaut
  module Rails
    module Matchers
      module Controllers

        class RedirectTo  #:nodoc:

          def initialize(request, expected)
            @expected = expected
            @request = request
          end

          def matches?(response)
            @redirected = response.redirect?
            @actual = response.redirect_url
            return false unless @redirected
            if @expected.instance_of? Hash
              return false unless @actual =~ %r{^\w+://#{@request.host}}
              return false unless actual_redirect_to_valid_route
              return actual_hash == expected_hash
            else
              return @actual == expected_url
            end
          end

          def actual_hash
            hash_from_url @actual
          end

          def expected_hash
            hash_from_url expected_url
          end

          def actual_redirect_to_valid_route
            actual_hash
          end

          def hash_from_url(url)
            query_hash(url).merge(path_hash(url)).with_indifferent_access
          end

          def path_hash(url)
            path = url.sub(%r{^\w+://#{@request.host}(?::\d+)?}, "").split("?", 2)[0]
            ActionController::Routing::Routes.recognize_path path
          end

          def query_hash(url)
            query = url.split("?", 2)[1] || ""
            QueryParameterParser.parse_query_parameters(query, @request)
          end

          def expected_url
            case @expected
            when Hash
              return ActionController::UrlRewriter.new(@request, {}).rewrite(@expected)
            when :back
              return @request.env['HTTP_REFERER']
            when %r{^\w+://.*}
              return @expected
            else
              return "http://#{@request.host}" + (@expected.split('')[0] == '/' ? '' : '/') + @expected
            end
          end

          def failure_message
            if @redirected
              return %Q{expected redirect to #{@expected.inspect}, got redirect to #{@actual.inspect}}
            else
              return %Q{expected redirect to #{@expected.inspect}, got no redirect}
            end
          end

          def negative_failure_message
            return %Q{expected not to be redirected to #{@expected.inspect}, but was} if @redirected
          end

          def description
            "redirect to #{@actual.inspect}"
          end

          class QueryParameterParser
            def self.parse_query_parameters(query, request)
              if defined?(CGIMethods)
                CGIMethods.parse_query_parameters(query)
              else
                request.class.parse_query_parameters(query)
              end
            end
          end
        end

        # :call-seq:
        #   response.should redirect_to(url)
        #   response.should redirect_to(:action => action_name)
        #   response.should redirect_to(:controller => controller_name, :action => action_name)
        #   response.should_not redirect_to(url)
        #   response.should_not redirect_to(:action => action_name)
        #   response.should_not redirect_to(:controller => controller_name, :action => action_name)
        #
        # Passes if the response is a redirect to the url, action or controller/action.
        # Useful in controller specs (integration or isolation mode).
        #
        # == Examples
        #
        #   response.should redirect_to("path/to/action")
        #   response.should redirect_to("http://test.host/path/to/action")
        #   response.should redirect_to(:action => 'list')
        def redirect_to(opts)
          RedirectTo.new(request, opts)
        end

      end
    end
  end
end