# frozen_string_literal: true

require "anycable/rails/channel_state"
require "anycable/rails/connection_factory"

module AnyCable
  module Rails
    class Railtie < ::Rails::Railtie # :nodoc:
      initializer "anycable.disable_action_cable_mount", after: "action_cable.set_configs" do |app|
        next unless AnyCable::Rails.enabled?

        app.config.action_cable.mount_path = nil
      end

      initializer "anycable.websocket_url", after: "action_cable.set_configs" do |app|
        next unless AnyCable::Rails.enabled?

        websocket_url = AnyCable.config.websocket_url.presence
        next unless websocket_url

        app.config.action_cable.url = websocket_url

        ActiveSupport.on_load(:action_cable) do
          ::ActionCable.server.config.url = websocket_url
        end
      end

      initializer "anycable.logger", after: "action_cable.logger" do |_app|
        AnyCable.logger = ::ActionCable.server.config.logger

        AnyCable.configure_server do
          server_logger = AnyCable.logger = ::ActionCable.server.config.logger

          # Broadcast server logs to STDOUT in development
          if ::Rails.env.development? &&
              !ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
            console = ActiveSupport::Logger.new($stdout)
            console.formatter = ::Rails.logger.formatter if ::Rails.logger.respond_to?(:formatter)
            console.level = ::Rails.logger.level if ::Rails.logger.respond_to?(:level)

            # Rails 7.1+
            if AnyCable.logger.respond_to?(:broadcast_to)
              AnyCable.logger.broadcast_to(console)
            elsif ActiveSupport::Logger.respond_to?(:broadcast)
              AnyCable.logger.extend(ActiveSupport::Logger.broadcast(console))
            elsif defined?(ActiveSupport::BroadcastLogger)
              AnyCable.logger = ActiveSupport::BroadcastLogger.new(AnyCable.logger, console)
            end
          end

          AnyCable.logger = ActiveSupport::TaggedLogging.new(AnyCable.logger) if server_logger.is_a?(::Logger)
        end

        # Add tagging middleware
        if AnyCable.logger.respond_to?(:tagged)
          require "anycable/rails/middlewares/log_tagging"

          AnyCable.middleware.use(AnyCable::Rails::Middlewares::LogTagging)
        end
      end

      initializer "anycable.object_serializer" do |_app|
        require "anycable/rails/object_serializer"
        AnyCable::Serializer.object_serializer = AnyCable::Rails::ObjectSerializer
      end

      initializer "anycable.executor" do |app|
        require "anycable/rails/middlewares/executor"
        # see https://github.com/rails/rails/pull/33469/files
        executor = app.config.reload_classes_only_on_change ? app.reloader : app.executor
        AnyCable.middleware.use(AnyCable::Rails::Middlewares::Executor.new(executor))

        if app.executor.respond_to?(:error_reporter)
          AnyCable.capture_exception do |ex, method, message|
            ::Rails.error.report(ex, handled: false, context: {method: method.to_sym, payload: message})
          end
        end

        if AnyCable.config.batch_broadcasts?
          if AnyCable.broadcast_adapter.respond_to?(:start_batching)
            app.executor.to_run { AnyCable.broadcast_adapter.start_batching }
            app.executor.to_complete { AnyCable.broadcast_adapter.finish_batching }
          else
            warn "[AnyCable] Auto-batching is enabled for broadcasts but your anycable version doesn't support it. Please, upgrade"
          end
        end
      end

      initializer "anycable.socket_id_tracking" do
        ActiveSupport.on_load(:action_controller) do
          require "anycable/rails/socket_id_tracking"
          include AnyCable::Rails::SocketIdTrackingController
        end

        ActiveSupport.on_load(:active_job) do
          require "anycable/rails/socket_id_tracking"
          include AnyCable::Rails::SocketIdTrackingJob
        end
      end

      initializer "anycable.connection_factory", after: "action_cable.set_configs" do |app|
        ActiveSupport.on_load(:action_cable) do
          app.config.to_prepare do
            AnyCable.connection_factory = AnyCable::Rails::ConnectionFactory.new
          end

          if AnyCable.config.persistent_session_enabled?
            require "anycable/rails/connections/persistent_session"
          end
        end
      end

      initializer "anycable.routes" do
        config.after_initialize do |app|
          config = AnyCable.config
          unless config.http_rpc_mount_path.nil?
            app.routes.prepend do
              mount AnyCable::HTTRPC::Server.new => config.http_rpc_mount_path, :internal => true
            end
          end
        end
      end

      initializer "anycable.verify_pool_sizes" do
        next if AnyCable.config.disable_rpc_pool_size_warning?

        # Log current db vs. gRPC pool sizes
        AnyCable.configure_server do
          # Skip if non-gRPC server is used
          next unless AnyCable.config.respond_to?(:rpc_pool_size)

          ActiveSupport.on_load(:active_record) do
            db_pool_size = ::ActiveRecord::Base.connection_pool.size
            rpc_pool_size = AnyCable.config.rpc_pool_size

            if rpc_pool_size > db_pool_size
              ::Kernel.warn(
                "\n⛔️ WARNING: AnyCable RPC pool size (#{rpc_pool_size}) is greater than DB pool size (#{db_pool_size})\n" \
                "Please, consider adjusting the database pool size to avoid connection wait times and increase throughput of your RPC server\n\n"
              )
            end
          end
        end
      end

      initializer "anycable.helpers" do
        ActiveSupport.on_load(:action_view) do
          include AnyCable::Rails::Helper
        end
      end

      initializer "anycable.warden_manager" do
        if defined?(::Devise) && AnyCable.config.use_warden_manager?
          AnyCable::Rails::Rack.middleware.use Warden::Manager do |config|
            ::Devise.warden_config = config
          end
        end
      end

      # Since Rails 6.1
      if respond_to?(:server)
        server do
          next unless AnyCable.config.embedded? && AnyCable::Rails.enabled?

          require "anycable/cli"
          AnyCable::CLI.embed!
        end
      end
    end
  end
end
