require 'volt/router/routes'
require 'volt/models'

def routes(&block)
  @routes = Volt::Routes.new
  @routes.define(&block)
end

describe Volt::Routes do
  it 'should setup direct routes' do
    routes do
      client '/', view: 'index'
      client '/page1', view: 'first_page'
      get '/page2', controller: 'page', action: 'show'
    end

    direct_routes = @routes.instance_variable_get(:@direct_routes)
    expect(direct_routes[:client]).to eq('/' => { view: 'index' }, '/page1' => { view: 'first_page' })
    expect(direct_routes[:get]).to eq('/page2' => { controller: 'page', action: 'show' })
  end

  it 'should setup indirect routes' do
    routes do
      client '/blog/{{ id }}/edit', view: 'blog/edit'
      client '/blog/{{ id }}', view: 'blog/show'
      get '/comments/{{ id }}/edit', controller: 'comments', action: 'edit'
      get '/comments/{{ id }}', controller: 'comments', action: 'show'
    end

    indirect_routes = @routes.instance_variable_get(:@indirect_routes)
    expect(indirect_routes[:client]).to eq(
      'blog' => {
        '*' => {
          'edit' => {
            nil => { view: 'blog/edit', id: 1 }
            },
            nil => { view: 'blog/show', id: 1 }
          }
        }
        )

    expect(indirect_routes[:get]).to eq(
      'comments' => {
        '*' => {
          'edit' => {
            nil => { controller: 'comments', action: 'edit', id: 1 }
            },
            nil => { controller: 'comments', action: 'show', id: 1 }
          }
        }
        )
  end

  it 'should setup param matchers' do
    routes do
      client '/blog', view: 'blog'
      client '/blog/{{ id }}', view: 'blog/show'
      client '/blog/{{ id }}/edit', view: 'blog/edit'
      client '/blog/tags/{{ tag }}', view: 'blog/tag'
      client '/login/{{ name }}/user/{{ id }}', view: 'login', action: 'user'
      get '/articles', controller: 'articles', action: 'index'
      get '/articles/{{ id }}', controller: 'articles', action: 'show'
    end

    param_matches = @routes.instance_variable_get(:@param_matches)
    expect(param_matches[:client].map { |v| v[0] }).to eq([
      { view: 'blog' },
      { view: 'blog/show', id: nil },
      { view: 'blog/edit', id: nil },
      { view: 'blog/tag', tag: nil },
      { view: 'login', action: 'user', name: nil, id: nil }
      ])

    expect(param_matches[:get].map { |v| v[0] }).to eq([
      { controller: 'articles', action: 'index' },
      { controller: 'articles', action: 'show', id: nil },
      ])
  end


  it 'should match routes' do
    routes do
      client '/blog', view: 'blog'
      client '/blog/{{ id }}', view: 'blog/show'
      client '/blog/{{ id }}/draft', view: 'blog/draft', action: 'draft'
      client '/blog/{{ id }}/edit', view: 'blog/edit'
      client '/blog/tags/{{ tag }}', view: 'blog/tag'
      client '/login/{{ name }}/user/{{ id }}', view: 'login', action: 'user'
      get '/articles', controller: 'articles', action: 'index'
      get '/articles/{{ id }}', controller: 'articles', action: 'show'
      put '/articles/{{ articles_id }}/comments/{{ id }}', controller: 'comments', action: 'update'
      post '/comments', controller: 'comments', action: 'create'
      put '/people', controller: 'people', action: 'update'
      patch '/people/1', controller: 'people', action: 'update'
      delete '/people/2', controller: 'people', action: 'destroy'

    end

    params = @routes.url_to_params('/blog')
    expect(params).to eq(view: 'blog')

    params = @routes.url_to_params('/blog/55/edit')
    expect(params).to eq(view: 'blog/edit', id: '55')

    params = @routes.url_to_params('/blog/55')
    expect(params).to eq(view: 'blog/show', id: '55')

    params = @routes.url_to_params('/blog/tags/good')
    expect(params).to eq(view: 'blog/tag', tag: 'good')

    params = @routes.url_to_params('/blog/55/draft')
    expect(params).to eq(view: 'blog/draft', id: '55', action: 'draft')

    params = @routes.url_to_params('/login/jim/user/10')
    expect(params).to eq(view: 'login', action: 'user', name: 'jim', id: '10')

    params = @routes.url_to_params('/login/cool')
    expect(params).to eq(false)

    params = @routes.url_to_params(:get, '/articles')
    expect(params).to eq(controller: 'articles', action: 'index')

    params = @routes.url_to_params('get', '/articles')
    expect(params).to eq(controller: 'articles', action: 'index')

    params = @routes.url_to_params(:post, '/articles')
    expect(params).to be_nil

    params = @routes.url_to_params(:post, '/comments')
    expect(params).to eq(controller: 'comments', action: 'create')

    params = @routes.url_to_params(:put, '/people')
    expect(params).to eq(controller: 'people', action: 'update')

    params = @routes.url_to_params(:patch, '/people/1')
    expect(params).to eq(controller: 'people', action: 'update')

    params = @routes.url_to_params(:delete, '/people/2')
    expect(params).to eq(controller: 'people', action: 'destroy')

    params = @routes.url_to_params(:get, '/articles/2')
    expect(params).to eq(controller: 'articles', action: 'show', id: '2')

    params = @routes.url_to_params(:put, '/articles/2/comments/9')
    expect(params).to eq(controller: 'comments', action: 'update', articles_id: '2', id: '9')
  end

  it 'should go from params to url' do
    routes do
      client '/blog', view: 'blog'
      client '/blog/{{ id }}', view: 'blog/show'
      client '/blog/{{ id }}/edit', view: 'blog/edit'
      client '/blog/tags/{{ tag }}', view: 'blog/tag'
      client '/login/{{ name }}/user/{{ id }}', view: 'login', action: 'user'
      get '/articles/{{ id }}', controller: 'articles', action: 'show'
      put '/articles/{{ id }}', controller: 'articles', action: 'update'
    end

    url, params = @routes.params_to_url(view: 'blog/show', id: '55')
    expect(url).to eq('/blog/55')
    expect(params).to eq({})

    url, params = @routes.params_to_url(view: 'blog/edit', id: '100')
    expect(url).to eq('/blog/100/edit')
    expect(params).to eq({})

    url, params = @routes.params_to_url(view: 'blog/edit', id: '100', other: 'should_pass')
    expect(url).to eq('/blog/100/edit')
    expect(params).to eq(other: 'should_pass')

    url, params = @routes.params_to_url(controller: 'articles', action: 'show', method: :get, id: 10)
    expect(url).to eq('/articles/10')
    expect(params).to eq({})

    url, params = @routes.params_to_url(controller: 'articles', action: 'update', method: :put, id: 99, other: 'xyz')
    expect(url).to eq('/articles/99')
    expect(params).to eq({other: 'xyz'})

    url, params = @routes.params_to_url({})
    expect(url).to eq(nil)
    expect(params).to eq(nil)
  end

  it 'should test that params match a param matcher' do
    routes = Volt::Routes.new
    match, params = routes.send(:check_params_match, { view: 'blog', id: '55' }, view: 'blog', id: nil)
    expect(match).to eq(true)
    expect(params).to eq(id: '55')

    match, params = routes.send(:check_params_match, { view: 'blog', id: '55' }, view: 'blog', id: '20')
    expect(match).to eq(false)

    match, params = routes.send(:check_params_match, { view: 'blog', name: { _title: 'Mr', name: 'Bob' }, id: '55' }, view: 'blog', id: nil, name: { _title: 'Mr', name: nil })
    expect(match).to eq(true)
    expect(params).to eq(id: '55')

    # Check with an extra value name.name
    match, params = routes.send(:check_params_match, { view: 'blog', name: { _title: 'Mr', name: 'Bob' }, id: '55' }, view: 'blog', id: nil, name: { _title: 'Mr' })
    expect(match).to eq(true)
    expect(params).to eq(id: '55')

    match, params = routes.send(:check_params_match, { view: 'blog', name: { _title: 'Mr', name: 'Bob' }, id: '55' }, view: 'blog', id: nil, name: { _title: 'Phd' })
    expect(match).to eq(false)

    # Check to make sure extra values in the params pass it.
    match, params = routes.send(:check_params_match, { view: 'blog', id: '55', _extra: 'some value' }, view: 'blog', id: '55')
    expect(match).to eq(true)
    expect(params).to eq(_extra: 'some value')

    match, params = routes.send(:check_params_match, { view: 'blog', id: '55' }, view: 'blog', id: '20')
    expect(match).to eq(false)
  end

  it 'should not match params that have no matches at all' do
    routes = Volt::Routes.new
    match, params = routes.send(:check_params_match, { view: '', id: false }, bleep: {volt: 'rocks'} )
    expect(match).to eq(false)
  end

  it 'should not match params that have a nil value' do
    routes = Volt::Routes.new
    match, params = routes.send(:check_params_match, { view: 'blog', id: false }, bleep: nil )
    expect(match).to eq(false)
  end

  it 'should match routes' do
    params = Volt::Model.new({}, persistor: Volt::Persistors::Params)
    params._controller = 'blog'
    params._index = '5'

    routes do
      client '/', controller: 'index'
      client '/blog', controller: 'blog'
    end

    path, cleaned_params = @routes.params_to_url(params.to_h)
    expect(path).to eq('/blog')
    expect(cleaned_params).to eq(index: '5')
  end

  it 'should handle routes with bindings in them' do
    params = Volt::Model.new({}, persistor: Volt::Persistors::Params)

    routes do
      client '/', controller: 'index'
      client '/blog/{{ id }}', controller: 'blog'
    end

    params = @routes.url_to_params('/blog/20')
  end
end