require "active_record_unit"
require "fixtures/project"

class Task < ActiveRecord::Base
  self.table_name = "projects"
end

class Step < ActiveRecord::Base
  self.table_name = "projects"
end

class Bid < ActiveRecord::Base
  self.table_name = "projects"
end

class Tax < ActiveRecord::Base
  self.table_name = "projects"
end

class Fax < ActiveRecord::Base
  self.table_name = "projects"
end

class Series < ActiveRecord::Base
  self.table_name = "projects"
end

class ModelDelegator
  def to_model
    ModelDelegate.new
  end
end

class ModelDelegate
  def persisted?
    true
  end

  def model_name
    ActiveModel::Name.new(self.class)
  end

  def to_param
    "overridden"
  end
end

module Blog
  class Post < ActiveRecord::Base
    self.table_name = "projects"
  end

  class Blog < ActiveRecord::Base
    self.table_name = "projects"
  end

  def self.use_relative_model_naming?
    true
  end
end

class PolymorphicRoutesTest < ActionController::TestCase
  include SharedTestRoutes.url_helpers
  self.default_url_options[:host] = "example.com"

  def setup
    @project = Project.new
    @task = Task.new
    @step = Step.new
    @bid = Bid.new
    @tax = Tax.new
    @fax = Fax.new
    @delegator = ModelDelegator.new
    @series = Series.new
    @blog_post = Blog::Post.new
    @blog_blog = Blog::Blog.new
  end

  def assert_url(url, args)
    host = self.class.default_url_options[:host]

    assert_equal url.sub(/http:\/\/#{host}/, ""), polymorphic_path(args)
    assert_equal url, polymorphic_url(args)
    assert_equal url, url_for(args)
  end

  def test_string
    with_test_routes do
      # FIXME: why are these different? Symbol case passes through to
      # `polymorphic_url`, but the String case doesn't.
      assert_equal "http://example.com/projects", polymorphic_url("projects")
      assert_equal "projects", url_for("projects")
    end
  end

  def test_string_with_options
    with_test_routes do
      assert_equal "http://example.com/projects?id=10", polymorphic_url("projects", id: 10)
    end
  end

  def test_symbol
    with_test_routes do
      assert_url "http://example.com/projects", :projects
    end
  end

  def test_symbol_with_options
    with_test_routes do
      assert_equal "http://example.com/projects?id=10", polymorphic_url(:projects, id: 10)
    end
  end

  def test_passing_routes_proxy
    with_namespaced_routes(:blog) do
      proxy = ActionDispatch::Routing::RoutesProxy.new(_routes, self, _routes.url_helpers)
      @blog_post.save
      assert_url "http://example.com/posts/#{@blog_post.id}", [proxy, @blog_post]
    end
  end

  def test_namespaced_model
    with_namespaced_routes(:blog) do
      @blog_post.save
      assert_url "http://example.com/posts/#{@blog_post.id}", @blog_post
    end
  end

  def test_namespaced_model_with_name_the_same_as_namespace
    with_namespaced_routes(:blog) do
      @blog_blog.save
      assert_url "http://example.com/blogs/#{@blog_blog.id}", @blog_blog
    end
  end

  def test_polymorphic_url_with_2_objects
    with_namespaced_routes(:blog) do
      @blog_blog.save
      @blog_post.save
      assert_equal "http://example.com/blogs/#{@blog_blog.id}/posts/#{@blog_post.id}", polymorphic_url([@blog_blog, @blog_post])
    end
  end

  def test_polymorphic_url_with_3_objects
    with_namespaced_routes(:blog) do
      @blog_blog.save
      @blog_post.save
      @fax.save
      assert_equal "http://example.com/blogs/#{@blog_blog.id}/posts/#{@blog_post.id}/faxes/#{@fax.id}", polymorphic_url([@blog_blog, @blog_post, @fax])
    end
  end

  def test_namespaced_model_with_nested_resources
    with_namespaced_routes(:blog) do
      @blog_post.save
      @blog_blog.save
      assert_url "http://example.com/blogs/#{@blog_blog.id}/posts/#{@blog_post.id}", [@blog_blog, @blog_post]
    end
  end

  def test_with_nil
    with_test_routes do
      exception = assert_raise ArgumentError do
        polymorphic_url(nil)
      end
      assert_equal "Nil location provided. Can't build URI.", exception.message
    end
  end

  def test_with_empty_list
    with_test_routes do
      exception = assert_raise ArgumentError do
        polymorphic_url([])
      end
      assert_equal "Nil location provided. Can't build URI.", exception.message
    end
  end

  def test_with_nil_id
    with_test_routes do
      exception = assert_raise ArgumentError do
        polymorphic_url(id: nil)
      end
      assert_equal "Nil location provided. Can't build URI.", exception.message
    end
  end

  def test_with_entirely_nil_list
    with_test_routes do
      exception = assert_raise ArgumentError do
        @series.save
        polymorphic_url([nil, nil])
      end
      assert_equal "Nil location provided. Can't build URI.", exception.message
    end
  end

  def test_with_nil_in_list_for_resource_that_could_be_top_level_or_nested
    with_top_level_and_nested_routes do
      @blog_post.save
      assert_equal "http://example.com/posts/#{@blog_post.id}", polymorphic_url([nil, @blog_post])
    end
  end

  def test_with_nil_in_list_does_not_generate_invalid_link
    with_top_level_and_nested_routes do
      exception = assert_raise NoMethodError do
        @series.save
        polymorphic_url([nil, @series])
      end
      assert_match(/undefined method `series_url'/, exception.message)
    end
  end

  def test_with_record
    with_test_routes do
      @project.save
      assert_url "http://example.com/projects/#{@project.id}", @project
    end
  end

  def test_with_class
    with_test_routes do
      assert_url "http://example.com/projects", @project.class
    end
  end

  def test_with_class_list_of_one
    with_test_routes do
      assert_url "http://example.com/projects", [@project.class]
    end
  end

  def test_class_with_options
    with_test_routes do
      assert_equal "http://example.com/projects?foo=bar", polymorphic_url(@project.class, foo: :bar)
      assert_equal "/projects?foo=bar", polymorphic_path(@project.class, foo: :bar)
    end
  end

  def test_with_new_record
    with_test_routes do
      assert_url "http://example.com/projects", @project
    end
  end

  def test_new_record_arguments
    params = nil

    with_test_routes do
      extend Module.new {
        define_method("projects_url") { |*args|
          params = args
          super(*args)
        }

        define_method("projects_path") { |*args|
          params = args
          super(*args)
        }
      }

      assert_url "http://example.com/projects", @project
      assert_equal [], params
    end
  end

  def test_with_destroyed_record
    with_test_routes do
      @project.destroy
      assert_url "http://example.com/projects", @project
    end
  end

  def test_with_record_and_action
    with_test_routes do
      assert_equal "http://example.com/projects/new", polymorphic_url(@project, action: "new")
    end
  end

  def test_url_helper_prefixed_with_new
    with_test_routes do
      assert_equal "http://example.com/projects/new", new_polymorphic_url(@project)
    end
  end

  def test_regression_path_helper_prefixed_with_new_and_edit
    with_test_routes do
      assert_equal "/projects/new", new_polymorphic_path(@project)

      @project.save
      assert_equal "/projects/#{@project.id}/edit", edit_polymorphic_path(@project)
    end
  end

  def test_url_helper_prefixed_with_edit
    with_test_routes do
      @project.save
      assert_equal "http://example.com/projects/#{@project.id}/edit", edit_polymorphic_url(@project)
    end
  end

  def test_url_helper_prefixed_with_edit_with_url_options
    with_test_routes do
      @project.save
      assert_equal "http://example.com/projects/#{@project.id}/edit?param1=10", edit_polymorphic_url(@project, param1: "10")
    end
  end

  def test_url_helper_with_url_options
    with_test_routes do
      @project.save
      assert_equal "http://example.com/projects/#{@project.id}?param1=10", polymorphic_url(@project, param1: "10")
    end
  end

  def test_format_option
    with_test_routes do
      @project.save
      assert_equal "http://example.com/projects/#{@project.id}.pdf", polymorphic_url(@project, format: :pdf)
    end
  end

  def test_format_option_with_url_options
    with_test_routes do
      @project.save
      assert_equal "http://example.com/projects/#{@project.id}.pdf?param1=10", polymorphic_url(@project, format: :pdf, param1: "10")
    end
  end

  def test_id_and_format_option
    with_test_routes do
      @project.save
      assert_equal "http://example.com/projects/#{@project.id}.pdf", polymorphic_url(id: @project, format: :pdf)
    end
  end

  def test_with_nested
    with_test_routes do
      @project.save
      @task.save
      assert_url "http://example.com/projects/#{@project.id}/tasks/#{@task.id}", [@project, @task]
    end
  end

  def test_with_nested_unsaved
    with_test_routes do
      @project.save
      assert_url "http://example.com/projects/#{@project.id}/tasks", [@project, @task]
    end
  end

  def test_with_nested_destroyed
    with_test_routes do
      @project.save
      @task.destroy
      assert_url "http://example.com/projects/#{@project.id}/tasks", [@project, @task]
    end
  end

  def test_with_nested_class
    with_test_routes do
      @project.save
      assert_url "http://example.com/projects/#{@project.id}/tasks", [@project, @task.class]
    end
  end

  def test_class_with_array_and_namespace
    with_admin_test_routes do
      assert_url "http://example.com/admin/projects", [:admin, @project.class]
    end
  end

  def test_new_with_array_and_namespace
    with_admin_test_routes do
      assert_equal "http://example.com/admin/projects/new", polymorphic_url([:admin, @project], action: "new")
    end
  end

  def test_unsaved_with_array_and_namespace
    with_admin_test_routes do
      assert_url "http://example.com/admin/projects", [:admin, @project]
    end
  end

  def test_nested_unsaved_with_array_and_namespace
    with_admin_test_routes do
      @project.save
      assert_url "http://example.com/admin/projects/#{@project.id}/tasks", [:admin, @project, @task]
    end
  end

  def test_nested_with_array_and_namespace
    with_admin_test_routes do
      @project.save
      @task.save
      assert_url "http://example.com/admin/projects/#{@project.id}/tasks/#{@task.id}", [:admin, @project, @task]
    end
  end

  def test_ordering_of_nesting_and_namespace
    with_admin_and_site_test_routes do
      @project.save
      @task.save
      @step.save
      assert_url "http://example.com/admin/projects/#{@project.id}/site/tasks/#{@task.id}/steps/#{@step.id}", [:admin, @project, :site, @task, @step]
    end
  end

  def test_nesting_with_array_ending_in_singleton_resource
    with_test_routes do
      @project.save
      assert_url "http://example.com/projects/#{@project.id}/bid", [@project, :bid]
    end
  end

  def test_nesting_with_array_containing_singleton_resource
    with_test_routes do
      @project.save
      @task.save
      assert_url "http://example.com/projects/#{@project.id}/bid/tasks/#{@task.id}", [@project, :bid, @task]
    end
  end

  def test_nesting_with_array_containing_singleton_resource_and_format
    with_test_routes do
      @project.save
      @task.save
      assert_equal "http://example.com/projects/#{@project.id}/bid/tasks/#{@task.id}.pdf", polymorphic_url([@project, :bid, @task], format: :pdf)
    end
  end

  def test_nesting_with_array_containing_namespace_and_singleton_resource
    with_admin_test_routes do
      @project.save
      @task.save
      assert_url "http://example.com/admin/projects/#{@project.id}/bid/tasks/#{@task.id}", [:admin, @project, :bid, @task]
    end
  end

  def test_nesting_with_array
    with_test_routes do
      @project.save
      assert_url "http://example.com/projects/#{@project.id}/bid", [@project, :bid]
    end
  end

  def test_with_array_containing_single_object
    with_test_routes do
      @project.save
      assert_url "http://example.com/projects/#{@project.id}", [@project]
    end
  end

  def test_with_array_containing_single_name
    with_test_routes do
      @project.save
      assert_url "http://example.com/projects", [:projects]
    end
  end

  def test_with_array_containing_single_string_name
    with_test_routes do
      assert_url "http://example.com/projects", ["projects"]
    end
  end

  def test_with_array_containing_symbols
    with_test_routes do
      assert_url "http://example.com/series/new", [:new, :series]
    end
  end

  def test_with_hash
    with_test_routes do
      @project.save
      assert_equal "http://example.com/projects/#{@project.id}", polymorphic_url(id: @project)
    end
  end

  def test_polymorphic_path_accepts_options
    with_test_routes do
      assert_equal "/projects/new", polymorphic_path(@project, action: "new")
    end
  end

  def test_polymorphic_path_does_not_modify_arguments
    with_admin_test_routes do
      @project.save
      @task.save

      options = {}
      object_array = [:admin, @project, @task]
      original_args = [object_array.dup, options.dup]

      assert_no_difference("object_array.size") { polymorphic_path(object_array, options) }
      assert_equal original_args, [object_array, options]
    end
  end

  # Tests for names where .plural.singular doesn't round-trip
  def test_with_irregular_plural_record
    with_test_routes do
      @tax.save
      assert_url "http://example.com/taxes/#{@tax.id}", @tax
    end
  end

  def test_with_irregular_plural_class
    with_test_routes do
      assert_url "http://example.com/taxes", @tax.class
    end
  end

  def test_with_irregular_plural_new_record
    with_test_routes do
      assert_url "http://example.com/taxes", @tax
    end
  end

  def test_with_irregular_plural_destroyed_record
    with_test_routes do
      @tax.destroy
      assert_url "http://example.com/taxes", @tax
    end
  end

  def test_with_irregular_plural_record_and_action
    with_test_routes do
      assert_equal "http://example.com/taxes/new", polymorphic_url(@tax, action: "new")
    end
  end

  def test_irregular_plural_url_helper_prefixed_with_new
    with_test_routes do
      assert_equal "http://example.com/taxes/new", new_polymorphic_url(@tax)
    end
  end

  def test_irregular_plural_url_helper_prefixed_with_edit
    with_test_routes do
      @tax.save
      assert_equal "http://example.com/taxes/#{@tax.id}/edit", edit_polymorphic_url(@tax)
    end
  end

  def test_with_nested_irregular_plurals
    with_test_routes do
      @tax.save
      @fax.save
      assert_equal "http://example.com/taxes/#{@tax.id}/faxes/#{@fax.id}", polymorphic_url([@tax, @fax])
    end
  end

  def test_with_nested_unsaved_irregular_plurals
    with_test_routes do
      @tax.save
      assert_url "http://example.com/taxes/#{@tax.id}/faxes", [@tax, @fax]
    end
  end

  def test_new_with_irregular_plural_array_and_namespace
    with_admin_test_routes do
      assert_equal "http://example.com/admin/taxes/new", polymorphic_url([:admin, @tax], action: "new")
    end
  end

  def test_class_with_irregular_plural_array_and_namespace
    with_admin_test_routes do
      assert_url "http://example.com/admin/taxes", [:admin, @tax.class]
    end
  end

  def test_unsaved_with_irregular_plural_array_and_namespace
    with_admin_test_routes do
      assert_url "http://example.com/admin/taxes", [:admin, @tax]
    end
  end

  def test_nesting_with_irregular_plurals_and_array_ending_in_singleton_resource
    with_test_routes do
      @tax.save
      assert_url "http://example.com/taxes/#{@tax.id}/bid", [@tax, :bid]
    end
  end

  def test_with_array_containing_single_irregular_plural_object
    with_test_routes do
      @tax.save
      assert_url "http://example.com/taxes/#{@tax.id}", [@tax]
    end
  end

  def test_with_array_containing_single_name_irregular_plural
    with_test_routes do
      @tax.save
      assert_url "http://example.com/taxes", [:taxes]
    end
  end

  # Tests for uncountable names
  def test_uncountable_resource
    with_test_routes do
      @series.save
      assert_url "http://example.com/series/#{@series.id}", @series
      assert_url "http://example.com/series", Series.new
    end
  end

  def test_routing_to_a_model_delegate
    with_test_routes do
      assert_url "http://example.com/model_delegates/overridden", @delegator
    end
  end

  def test_nested_routing_to_a_model_delegate
    with_test_routes do
      assert_url "http://example.com/foo/model_delegates/overridden", [:foo, @delegator]
    end
  end

  def with_namespaced_routes(name)
    with_routing do |set|
      set.draw do
        scope(module: name) do
          resources :blogs do
            resources :posts do
              resources :faxes
            end
          end
          resources :posts
        end
      end

      extend @routes.url_helpers
      yield
    end
  end

  def with_test_routes(options = {})
    with_routing do |set|
      set.draw do
        resources :projects do
          resources :tasks
          resource :bid do
            resources :tasks
          end
        end
        resources :taxes do
          resources :faxes
          resource :bid
        end
        resources :series
        resources :model_delegates
        namespace :foo do
          resources :model_delegates
        end
      end

      extend @routes.url_helpers
      yield
    end
  end

  def with_top_level_and_nested_routes(options = {})
    with_routing do |set|
      set.draw do
        resources :blogs do
          resources :posts
          resources :series
        end
        resources :posts
      end

      extend @routes.url_helpers
      yield
    end
  end

  def with_admin_test_routes(options = {})
    with_routing do |set|
      set.draw do
        namespace :admin do
          resources :projects do
            resources :tasks
            resource :bid do
              resources :tasks
            end
          end
          resources :taxes do
            resources :faxes
          end
          resources :series
        end
      end

      extend @routes.url_helpers
      yield
    end
  end

  def with_admin_and_site_test_routes(options = {})
    with_routing do |set|
      set.draw do
        namespace :admin do
          resources :projects do
            namespace :site do
              resources :tasks do
                resources :steps
              end
            end
          end
        end
      end

      extend @routes.url_helpers
      yield
    end
  end
end

class PolymorphicPathRoutesTest < PolymorphicRoutesTest
  include ActionView::RoutingUrlFor
  include ActionView::Context

  attr_accessor :controller

  def assert_url(url, args)
    host = self.class.default_url_options[:host]

    assert_equal url.sub(/http:\/\/#{host}/, ""), url_for(args)
  end
end