# frozen_string_literal: true

require 'test_helper'

class ChildResourceTester < Litany::Resource
  include Litany::Taggable

  id_validator /^id-[a-z]{1,20}$/

  parent_resource :tester
  property :test_property, nil, [nil, Integer], inherited: true
  property_collection :collection, [], [nil, String], inherited: true

  resource_reference ChildResourceTester, :child_ref, inherited: true
  resource_references ChildResourceTester, :sub_ref, inherited: true
end

class CustomChildResourceTester < Litany::Resource
  class << self
    def child_resource_name(parent, name)
      "custom_child_of_#{parent.name}"
    end
  end
end

class DSLTester < Litany::Resource
  extend Litany::ResourceDSL
  include Litany::Taggable

  child_resource ChildResourceTester, :child
  child_resource CustomChildResourceTester, :custom_child

  property :test_property, nil, [nil, Integer]
  property :nil_test_property, nil, [Integer]
  property :munged_property, nil, [Symbol, String] do |value|
    value.to_s.upcase
  end

  property_collection :optional_collection, [], [nil, Integer, String], required: false
  property_collection :collection, [], [nil, Integer, String] do |value|
    value == '+' ? :bad_value : value
  end

  resource_collection ChildResourceTester, :sub
  resource_collection ChildResourceTester, :optional_sub, required: false

  resource_reference ChildResourceTester, :child_ref
  resource_reference ChildResourceTester, :optional_child_ref, required: false

  resource_references ChildResourceTester, :sub_ref
end

class ResourceInheritanceTester < ChildResourceTester
  child_resource ChildResourceTester, :special_child
  property :special_property, nil, nil
end

class ResourceDSLTest < Minitest::Test
  def test_validity
    # true states
    assert DSLTester._valid_value?([String, Integer], '50')
    assert DSLTester._valid_value?(String, 'test string')
    assert DSLTester._valid_value?(Litany::ResourceDSL, DSLTester)
    assert DSLTester._valid_value?(nil, nil)
    assert DSLTester._valid_value?(NilClass, nil)
    assert DSLTester._valid_value?(1..5, 2)
    assert DSLTester._valid_value?(/^(\d+)$/, '50')
    assert DSLTester._valid_value?(true, true)

    assert DSLTester._valid_values?([String, Integer], [1, 2, 3, 50, 'Xarth', 'Twilight', 'Stand Up!'])

    # false states
    refute DSLTester._valid_value?([String, Integer], nil)
    refute DSLTester._valid_value?(String, 5)
    refute DSLTester._valid_value?(Litany::ResourceDSL, String)
    refute DSLTester._valid_value?(nil, true)
    refute DSLTester._valid_value?(NilClass, :bob_dole)
    refute DSLTester._valid_value?(1..5, 20)
    refute DSLTester._valid_value?(/^(\d+)$/, 50)
    refute DSLTester._valid_value?(true, false)

    refute DSLTester._valid_values?([String, Integer], [1, 2, 3, 50.0, 'Xarth', :Twilight, 'Stand Up!'])
  end

  def test_child_resource
    tester = DSLTester.new(:child_tester)
    child = tester.child

    assert_instance_of ChildResourceTester, child
    assert_equal child, tester.child
  end

  def test_child_resource_name
    tester = DSLTester.new(:child_name_tester)
    assert_equal :CustomChildOfChildNameTesterCustomChildResourceTester, tester.custom_child.resource_name
  end

  def test_id_validator
    assert_nil DSLTester.id_validator
    assert_raises { DSLTester.id_validator 55 }

    regex = /^id-[a-z]{1,20}$/
    DSLTester.id_validator regex

    assert_equal DSLTester.id_validator, regex
    assert_raises { DSLTester.id_validator /^not-id$/ }
  end

  def test_output_self
    refute DSLTester.output_self?
    DSLTester.output_self
    assert DSLTester.output_self
  end

  def test_parent_resource
    tester = DSLTester.new(:parent_resource)

    assert_equal tester, tester.child.tester
  end

  def test_property
    tester = DSLTester.new(:property_tester)

    refute tester.set_test_property?
    assert_nil tester.test_property

    tester.test_property 50
    assert_equal 50, tester.test_property

    assert_raises { tester.test_property :bad_value }
    assert tester.set_test_property?

    tester.test_property = nil
    assert_nil tester.test_property
  end

  def test_property_nil
    tester = DSLTester.new(:property_nil)

    refute tester.validate_nil_test_property
    refute tester.validate_nil_test_property

    tester.nil_test_property nil

    refute tester.validate_nil_test_property
  end

  def test_munged_property
    tester = DSLTester.new(:munged_property)

    tester.munged_property :omg
    assert_equal 'OMG', tester.munged_property
  end

  def test_property_inheritance
    tester = DSLTester.new(:property_inheritance)
    child = tester.child

    refute tester.set_test_property?
    refute child.set_test_property?

    assert_nil child.test_property

    tester.test_property 50
    assert_equal 50, child.test_property

    assert child.set_test_property?
    refute child.set_test_property?(false)

    child.test_property 20
    assert_equal 50, tester.test_property
    assert_equal 20, child.test_property
  end

  def test_property_collection
    tester = DSLTester.new(:property_collection)

    refute tester.set_collections?
    refute tester.validate_collections
    refute tester.set_collections?

    tester.collection 50

    assert_raises { tester.collection :bad_value }
    assert_raises { tester.collection '+' }

    assert tester.validate_collections
    assert_equal 1, tester.collections.size

    assert tester.validate_optional_collections
  end

  def test_property_collection_inheritance
    tester = DSLTester.new(:property_collection_inheritance)
    child = tester.child

    refute tester.set_collections?
    refute child.set_collections?

    assert_empty child.collections

    tester.collection 50
    assert_equal [50], tester.collections
    assert_equal [50], child.collections

    assert child.set_collections?
    refute child.set_collections?(false)

    refute child.validate_collections

    child.collection '20'
    assert_equal [50], tester.collections
    assert_equal ['20'], child.collections
  end

  def test_resource_collection
    tester = DSLTester.new(:resource_collection)

    refute tester.set_subs?
    refute tester.validate_subs

    tester.sub :sub1
    tester.sub :sub2

    assert tester.validate_subs
    assert_equal 2, tester.subs.size

    assert tester.validate_optional_subs
  end

  def test_resource_reference_tokens
    tester = DSLTester.new(:resource_reference)

    refute tester.set_child_ref?
    refute tester.validate_child_ref

    child_ref = ChildResourceTester.new(:child_ref)
    tester.child_ref :child_ref
    assert tester.set_child_ref?
    assert_equal child_ref, tester.child_ref

    tester.child_ref :child_ref

    assert tester.validate_child_ref
  end

  def test_resource_reference_instance
    tester = DSLTester.new(:resource_reference_2)
    child_ref = ChildResourceTester.new(:child_ref_2)

    refute tester.set_child_ref?

    tester.child_ref child_ref
    assert tester.set_child_ref?
    assert_equal child_ref, tester.child_ref

    tester.child_ref :child_ref_2
    assert_raises { tester.child_ref :child_ref }

    assert tester.validate_child_ref
  end

  def test_resource_reference_id
    tester = DSLTester.new(:resource_reference_3)

    assert_raises { tester.child_ref 'id-1231234' }

    tester.child_ref 'id-alskdfjl'
    assert_equal 'id-alskdfjl', tester.child_ref

    assert tester.validate_child_ref
  end

  def test_resource_reference_optionals
    tester = DSLTester.new(:resource_reference_4)
    assert tester.validate_optional_child_ref
  end

  def test_resource_reference_inheritance
    tester = DSLTester.new(:resource_reference_inheritance)
    child = tester.child

    refute tester.set_child_ref?
    refute child.set_child_ref?

    tester.child_ref :child_ref
    assert tester.set_child_ref?
    assert child.set_child_ref?
    refute child.set_child_ref?(false)
    assert_equal ChildResourceTester.new(:child_ref), child.child_ref

    child.child_ref :child_ref_override
    assert child.set_child_ref?(false)
    refute_equal tester.child_ref, child.child_ref
  end

  def test_resource_references
    tester = DSLTester.new(:resource_references)
    sub_ref_1 = ChildResourceTester.new(:sub_ref_1)
    sub_ref_2 = ChildResourceTester.new(:sub_ref_2)
    sub_ref_3 = 'id-asdlkfjasd'

    refute tester.set_sub_refs?
    refute tester.validate_sub_refs
    refute tester.set_sub_refs?

    tester.sub_ref :sub_ref_1
    tester.sub_ref sub_ref_2, sub_ref_3

    assert_raises { tester.sub_ref 'sub_ref' }
    assert_raises { tester.sub_ref 'id-1234'}

    assert tester.validate_sub_refs
    assert_equal [sub_ref_1, sub_ref_2, sub_ref_3], tester.sub_refs

    assert tester.validate_sub_refs
  end

  def test_resource_references_inheritance
    tester = DSLTester.new(:resource_references_inheritance)
    child = tester.child

    refute tester.set_sub_refs?
    refute child.set_sub_refs?

    assert_empty child.sub_refs

    tester.sub_ref 'id-asdf'
    assert_equal ['id-asdf'], tester.sub_refs
    assert_equal ['id-asdf'], child.sub_refs

    assert child.set_sub_refs?
    refute child.set_sub_refs?(false)

    assert child.validate_sub_refs

    child.sub_ref 'id-qwert'
    assert_equal ['id-asdf'], tester.sub_refs
    assert_equal ['id-qwert'], child.sub_refs
  end

  def test_resource_references_list
    tester = DSLTester.new(:resource_references_list)
    children = [ChildResourceTester.new(:child), ChildResourceTester.new(:child2), ChildResourceTester.new(:child3)]

    refute tester.set_sub_refs?

    tester.sub_ref [:child, :child2, :child3]
    assert_equal children, tester.sub_refs
    assert tester.set_sub_refs?
  end

  def test_tag_key
    tester = DSLTester.new(:tag_key)
    assert_equal :Tags, tester.tag_key

    DSLTester.tag_key :TagKey
    assert_equal :TagKey, tester.tag_key
    assert_equal :Tags, tester.child.tag_key
  end

  def test_resource_inheritance
    ChildResourceTester.output_self

    ChildResourceTester.finalize_resource
    ResourceInheritanceTester.finalize_resource

    resource = ChildResourceTester.new :resource
    sub_resource = ResourceInheritanceTester.new :resource

    refute_equal resource, sub_resource

    assert_equal ChildResourceTester.automatic_children_resources + Set.new([:special_child]), ResourceInheritanceTester.automatic_children_resources
    assert_equal true, ResourceInheritanceTester.output_self?
    assert_equal ChildResourceTester.properties + Set.new([:special_child, :special_property]), ResourceInheritanceTester.properties
    assert_equal ChildResourceTester.validators + Set.new([:validate_special_property]), ResourceInheritanceTester.validators
  end
end
