# frozen_string_literal: true

require_relative '../test_helper'

class ConditionalAttributesTest < Minitest::Test
  class User
    attr_accessor :id, :name, :profile, :articles

    def initialize(id, name)
      @id = id
      @name = name
      @articles = []
    end
  end

  class Article
    attr_accessor :user_id, :title, :body

    def initialize(user_id, title, body)
      @user_id = user_id
      @title = title
      @body = body
    end
  end

  class Profile
    attr_accessor :user_id, :email

    def initialize(user_id, email)
      @user_id = user_id
      @email = email
    end
  end

  class UserResource
    include Alba::Resource

    attributes :id
  end

  class ArticleResource
    include Alba::Resource

    attributes :title
  end

  class ProfileResource
    include Alba::Resource

    attributes :email
  end

  class UserResource1 < UserResource
    attributes :name, if: proc { |_user, name| name.size >= 5 }
  end

  class UserResource2 < UserResource
    attribute :username, if: proc { |user, _username| user.id == 1 } do
      'username'
    end
  end

  class UserResource3 < UserResource
    one :profile, resource: ProfileResource, if: proc { |_user, profile| profile.email.end_with?('com') }
  end

  class UserResource4 < UserResource
    many :articles, resource: ArticleResource, if: proc { |_user, articles| !articles.empty? }
  end

  class UserResource6 < UserResource
    attributes :full_name, if: proc { |user| user.respond_to?(:full_name) }
  end

  class UserResource7 < UserResource
    attributes :name, if: proc { |_user| print 'foo' or true }
  end

  class UserResource8 < UserResource
    attributes :name, if: proc { !!params[:should_have_name] }
  end

  class UserResource9 < UserResource
    attributes :name, if: proc { |_user, _name| !!params[:should_have_name] }
  end

  def setup
    @user = User.new(1, 'Masafumi OKURA')
    profile = Profile.new(1, 'test@example.com')
    @user.profile = profile
    article = Article.new(1, 'Hello World!', 'Hello World!!!')
    @user.articles << article
  end

  def test_conditional_attributes_with_if
    assert_equal(
      '{"id":1,"name":"Masafumi OKURA"}',
      UserResource1.new(@user).serialize
    )
    user = User.new(2, 'Foo')
    assert_equal(
      '{"id":2}',
      UserResource1.new(user).serialize
    )
    assert_equal(
      '[{"id":1,"name":"Masafumi OKURA"},{"id":2}]',
      UserResource1.new([@user, user]).serialize
    )
  end

  def test_conditional_attribute_with_if
    assert_equal(
      '{"id":1,"username":"username"}',
      UserResource2.new(@user).serialize
    )
    user = User.new(2, 'Foo')
    assert_equal(
      '{"id":2}',
      UserResource2.new(user).serialize
    )
    assert_equal(
      '[{"id":1,"username":"username"},{"id":2}]',
      UserResource2.new([@user, user]).serialize
    )
  end

  def test_conditional_one_with_if
    assert_equal(
      '{"id":1,"profile":{"email":"test@example.com"}}',
      UserResource3.new(@user).serialize
    )
    user = User.new(2, 'Foo')
    profile = Profile.new(2, 'test@example.org')
    user.profile = profile
    assert_equal(
      '{"id":2}',
      UserResource3.new(user).serialize
    )
  end

  def test_conditional_many_with_if
    assert_equal(
      '{"id":1,"articles":[{"title":"Hello World!"}]}',
      UserResource4.new(@user).serialize
    )
    user = User.new(2, 'Foo')
    assert_equal(
      '{"id":2}',
      UserResource4.new(user).serialize
    )
  end

  def test_conditional_attribute_with_if_with_only_one_parameter
    assert_equal(
      '{"id":1}',
      UserResource6.new(@user).serialize
    )
  end

  def test_conditional_attribute_with_if_with_one_parameter_yields_only_once
    assert_output('foo') { UserResource7.new(@user).serialize }
  end

  def test_conditional_attributes_with_params_in_if_without_block_parameters
    assert_equal(
      '{"id":1,"name":"Masafumi OKURA"}',
      UserResource8.new(@user, params: {should_have_name: true}).serialize
    )
    assert_equal(
      '{"id":1}',
      UserResource8.new(@user, params: {should_have_name: false}).serialize
    )
    assert_equal(
      '{"id":1}',
      UserResource8.new(@user).serialize
    )
  end

  def test_conditional_attributes_with_params_in_if_with_block_parameters
    assert_equal(
      '{"id":1,"name":"Masafumi OKURA"}',
      UserResource9.new(@user, params: {should_have_name: true}).serialize
    )
    assert_equal(
      '{"id":1}',
      UserResource9.new(@user, params: {should_have_name: false}).serialize
    )
    assert_equal(
      '{"id":1}',
      UserResource9.new(@user).serialize
    )
  end

  class UserResource10 < UserResource
    attributes :name, if: :method_returning_true

    def method_returning_true
      true
    end
  end

  class UserResource11 < UserResource
    attributes :name, if: :method_returning_false

    def method_returning_false
      false
    end
  end

  def test_conditional_attributes_with_if_with_symbol_method_name
    assert_equal(
      '{"id":1,"name":"Masafumi OKURA"}',
      UserResource10.new(@user).serialize
    )
    assert_equal(
      '{"id":1}',
      UserResource11.new(@user).serialize
    )
  end

  class UserResource12
    include Alba::Resource

    attributes :id, :name, if: proc { |_user, attribute| !attribute.nil? }
  end

  def test_conditional_attributes_with_if_for_nil_attributes
    @user = User.new(1, nil)

    assert_equal(
      '{"id":1}',
      UserResource12.new(@user).serialize
    )
  end

  class UserResource13 < UserResource
    attributes :name, if: proc { true }

    def name(_object)
      'From resource method'
    end
  end

  def test_conditional_attributes_with_if_and_resource_method
    assert_equal(
      '{"id":1,"name":"From resource method"}',
      UserResource13.new(@user).serialize
    )
  end

  class UserResource14 < UserResource
    one :profile, resource: ProfileResource, key: :my_profile, if: proc { |_user, profile| profile.email.end_with?('com') }
  end

  def test_conditional_one_with_key_option
    assert_equal(
      '{"id":1,"my_profile":{"email":"test@example.com"}}',
      UserResource14.new(@user).serialize
    )
    user = User.new(2, 'Foo')
    profile = Profile.new(2, 'test@example.org')
    user.profile = profile
    assert_equal(
      '{"id":2}',
      UserResource14.new(user).serialize
    )
  end

  class UserResource15 < UserResource
    many :articles,
         proc { |articles| articles }, # dummy
         resource: ArticleResource,
         if: proc { |_user, articles| !articles.empty? }
  end

  def test_conditional_many_with_condition_proc
    assert_equal(
      '{"id":1,"articles":[{"title":"Hello World!"}]}',
      UserResource15.new(@user).serialize
    )
    user = User.new(2, 'Foo')
    assert_equal(
      '{"id":2}',
      UserResource15.new(user).serialize
    )
  end

  class Foo
    attr_reader :id, :name

    def initialize(id, name)
      @id = id
      @name = name
    end
  end

  class FooResource
    include Alba::Resource

    attributes :id

    nested :bar, if: proc { |foo, _| params[:flag] || foo.id == 42 } do # dummy second parameter to test it doesn't raise an exception
      attributes :name
      attribute :baz do
        'baz'
      end
    end
  end

  def test_conditional_attributes_with_nested_attributes
    foo = Foo.new(1, 'name')
    assert_equal(
      '{"id":1,"bar":{"name":"name","baz":"baz"}}',
      FooResource.new(foo, params: {flag: true}).serialize
    )
    assert_equal(
      '{"id":1}',
      FooResource.new(foo, params: {flag: false}).serialize
    )
    foo42 = Foo.new(42, 'name')
    assert_equal(
      '{"id":42,"bar":{"name":"name","baz":"baz"}}',
      FooResource.new(foo42, params: {flag: false}).serialize
    )
  end

  class FooTypedResource
    include Alba::Resource

    attributes id: Integer
    attributes name: :String, if: proc { params[:flag] }
  end

  def test_conditional_attributes_with_type
    foo = Foo.new(1, 'name')
    assert_equal(
      '{"id":1,"name":"name"}',
      FooTypedResource.new(foo, params: {flag: true}).serialize
    )
    assert_equal(
      '{"id":1}',
      FooTypedResource.new(foo, params: {flag: false}).serialize
    )
  end

  class FooTypedResource2
    include Alba::Resource

    attributes id: Integer
    attributes name: :String, if: proc { params[:flag] }

    def name(_object)
      'From resource method'
    end
  end

  def test_conditional_attributes_with_type_and_resource_method
    foo = Foo.new(1, 'name')
    assert_equal(
      '{"id":1,"name":"From resource method"}',
      FooTypedResource2.new(foo, params: {flag: true}).serialize
    )
    assert_equal(
      '{"id":1}',
      FooTypedResource2.new(foo, params: {flag: false}).serialize
    )
  end

  class FooTypedResource3
    include Alba::Resource

    attributes id: Integer
    attributes name: :String, if: proc { |foo, name| foo.id == 1 || name == 'my name' }
  end

  def test_conditional_attributes_with_type_with_two_arity_condition
    foo1 = Foo.new(1, 'name')
    assert_equal(
      '{"id":1,"name":"name"}',
      FooTypedResource3.new(foo1).serialize
    )
    foo2 = Foo.new(2, 'name')
    assert_equal(
      '{"id":2}',
      FooTypedResource3.new(foo2).serialize
    )
    foo3 = Foo.new(3, 'my name')
    assert_equal(
      '{"id":3,"name":"my name"}',
      FooTypedResource3.new(foo3).serialize
    )
  end
end
