# frozen_string_literal: true

module ActiveRecord
  module Tenanted
    module Testing
      module ActiveSupportTestCase
        extend ActiveSupport::Concern

        prepended do
          if klass = ActiveRecord::Tenanted.connection_class
            klass.current_tenant = Rails.application.config.active_record_tenanted.default_tenant
            tenant_genesis(klass)

            parallelize_setup do |worker|
              # free up the connection pool for the tenant name
              klass.destroy_tenant(klass.current_tenant)

              # destroy and create tenant databases for this worker's unique id
              klass.tenanted_root_config.test_worker_id = worker
              tenant_genesis(klass)
            end
          end
        end

        class_methods do
          private
            # Destroy any existing tenants from the last test run, and create a fresh tenant
            # database for the current tenant.
            #
            # Yes, this is a Star Trek reference.
            def tenant_genesis(klass)
              klass.tenants.each { |tenant| klass.destroy_tenant(tenant) }
              klass.create_tenant(klass.current_tenant)
            end
        end
      end

      module ActionDispatchIntegrationTest
        extend ActiveSupport::Concern

        prepended do
          setup do
            if klass = ActiveRecord::Tenanted.connection_class
              integration_session.host = "#{klass.current_tenant}.example.com"
            end
          end
        end
      end

      module ActionDispatchIntegrationSession
        def process(...)
          if klass = ActiveRecord::Tenanted.connection_class
            klass.without_tenant { super }
          else
            super
          end
        end
      end

      module ActionDispatchSystemTestCase
        extend ActiveSupport::Concern

        prepended do
          setup do
            if klass = ActiveRecord::Tenanted.connection_class
              self.default_url_options = { host: "#{klass.current_tenant}.example.localhost" }
            end
          end
        end
      end

      module ActiveRecordFixtures
        def transactional_tests_for_pool?(pool)
          config = pool.db_config

          # Prevent the tenanted BaseConfig from creating transactional fixtures on an unnecessary
          # database, which would result in sporadic locking errors.
          is_root_config = config.instance_of?(Tenanted::DatabaseConfigurations::BaseConfig)

          # Any tenanted database that isn't the default test fixture database should not be wrapped
          # in a transaction, for a couple of reasons:
          #
          # 1. we migrate the database using a temporary pool, which will wrap the schema load in a
          #    transaction that will not be visible to any connection used by the code under test to
          #    insert data.
          # 2. having an open transaction will prevent the test from being able to destroy the tenant.
          is_non_default_tenant = (
            config.instance_of?(Tenanted::DatabaseConfigurations::TenantConfig) &&
            config.tenant != Rails.application.config.active_record_tenanted.default_tenant.to_s
          )

          return false if is_root_config || is_non_default_tenant

          super
        end
      end

      module ActiveJobTestCase
        def perform_enqueued_jobs(...)
          if klass = ActiveRecord::Tenanted.connection_class
            klass.without_tenant { super }
          else
            super
          end
        end
      end

      module ActionCableTestCase
        def connect(path = ActionCable.server.config.mount_path, **request_params)
          if (klass = ActiveRecord::Tenanted.connection_class) && klass.current_tenant
            env = request_params.fetch(:env, {})
            env["HTTP_HOST"] ||= "#{klass.current_tenant}.example.com"
            request_params[:env] = env
          end

          super
        end
      end
    end
  end
end
