# frozen_string_literal: true

require 'factory_bot'

#
# Override factory registration to work with Resource and its relationships
# and attribute types.
#
def register_factory(args = {}) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity
  prefix         = args.fetch(:prefix)
  resource_class = args.fetch(:resource_class)
  key            = "#{prefix}_#{resource_class.resource_type}"

  FactoryBot.define do
    factory key, class: resource_class do
      resource_class.resource_attributes.each do |attribute, resource_attribute|
        case resource_attribute.type
        when LedgerSync::Type::StringFromSet
          add_attribute(attribute) { resource_attribute.type.values.first }
        when LedgerSync::Type::String, LedgerSync::Type::ID
          case attribute.to_sym
          when :ledger_id
            sequence(attribute) { nil }
          else # when :external_id or something else
            sequence(attribute) { |n| "#{attribute}-#{rand_id(n)}" }
          end
        when LedgerSync::Type::Float
          add_attribute(attribute) { 1.23 }
        when LedgerSync::Type::Boolean
          add_attribute(attribute) { false }
        when LedgerSync::Type::Date
          add_attribute(attribute) { Date.today }
        when LedgerSync::Type::Hash
          add_attribute(attribute) { {} }
        when LedgerSync::Type::Integer
          add_attribute(attribute) { 123 }
        when LedgerSync::Type::ReferenceOne
          key_ending = if resource_attribute.type.resource_class.is_a?(Array)
                         resource_attribute.type.resource_class.first.resource_type
                       else
                         resource_attribute.type.resource_class.resource_type
                       end
          resource_attribute_key = "#{prefix}_#{key_ending}"
          next if resource_attribute_key == key

          references_one attribute, factory: resource_attribute_key
        when LedgerSync::Type::ReferenceMany
          key_ending = if resource_attribute.type.resource_class.is_a?(Array)
                         resource_attribute.type.resource_class.first.resource_type
                       else
                         resource_attribute.type.resource_class.resource_type
                       end
          resource_attribute_key = "#{prefix}_#{key_ending}"
          next if resource_attribute_key == key

          references_many attribute, factory: resource_attribute_key
        else
          raise "Do not know how to default type: #{resource_attribute.type.class.name}"
        end
      end
    end
  end
end

def generate_resource_factories
  LedgerSync.ledgers.each do |ledger_key, ledger|
    ledger.client_class.resources.each do |resource_key, resource_class|
      factory_key = "#{ledger_key}_#{resource_key}".to_sym
      next if FactoryBot.factories.registered?(factory_key)

      register_factory(prefix: ledger_key, resource_class: resource_class)
    end
  end
end

RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
  config.before(:suite) do
    FactoryBot.find_definitions
    generate_resource_factories
  end
  config.after { FactoryBot.rewind_sequences }
end

FactoryBot.define do
  skip_create
  initialize_with { new(attributes) } # Allows initializing read-only attrs
end

module FactoryBot
  def self.rand_id(*appends, include_test_run_id: true)
    ret = ''
    ret += test_run_id if include_test_run_id
    ret += (0...8).map { rand(65..90).chr }.join
    appends.each do |append|
      ret += "-#{append}"
    end
    ret
  end

  def self.test_run_id(*appends, **keywords)
    @test_run_id ||= rand_id(
      *appends,
      **keywords.merge(include_test_run_id: false)
    )
  end

  class DefinitionProxy
    #
    # Helper method of our references_one on resources
    #
    # @param [String] name
    # @param [String] factory Defaults to name
    #
    # @return [FactoryBot::Declaration]
    #
    def references_one(name, factory: nil)
      add_attribute(name) do
        factory ||= name
        FactoryBot.build(factory, **attributes_for(factory))
      end
    end

    #
    # Helper method of our references_many on resources
    #
    # @param [String] name
    # @param [String] factory Defaults to name
    #
    # @return [FactoryBot::Declaration]
    #
    def references_many(name, count: 0, factory: nil)
      add_attribute(name) do
        factory ||= name
        FactoryBot.build_list(factory, count)
      end
    end
  end
end

#
# Used for uniqueness in QuickBooks Online given the unique name requirement
#
# @return [String]
#
def rand_id(*args)
  FactoryBot.rand_id(*args)
end

#
# Used for uniqueness in QuickBooks Online given the unique name requirement
#
# @return [String]
#
def test_run_id(*args)
  @test_run_id ||= FactoryBot.test_run_id(*args)
end