require "active_support/lazy_load_hooks"

module ActiveRecord::Turntable
  module Base
    extend ActiveSupport::Concern

    included do
      class_attribute :turntable_clusters, :turntable_sequencers,
                      :turntable_enabled, :turntable_sequencer_enabled, :turntable_configuration

      self.turntable_clusters = {}.with_indifferent_access
      self.turntable_sequencers = {}.with_indifferent_access
      self.turntable_enabled = false
      self.turntable_sequencer_enabled = false

      class << self
        delegate :shards_transaction, :with_all, to: :connection

        def reset_turntable_configuration(configuration, reset = true)
          old = self.turntable_configuration
          self.turntable_configuration = configuration

          old.release! if old

          if reset
            # TODO: replace exitsting connection_pool when configurations reloaded
            self.turntable_clusters = turntable_configuration.clusters
            self.turntable_sequencers = turntable_configuration.sequencers
            ActiveSupport.run_load_hooks(:turntable_configuration_loaded, ActiveRecord::Base)
          end
        end
      end

      include ClusterHelperMethods
    end

    module ClassMethods
      # @param [Symbol] cluster_name cluster name for this class
      # @param [Symbol] shard_key_name shard key attribute name
      # @param [Hash] options
      def turntable(cluster_name, shard_key_name, options = {})
        class_attribute :turntable_shard_key, :turntable_cluster_name

        self.turntable_enabled = true
        self.turntable_cluster_name = cluster_name
        self.turntable_shard_key = shard_key_name
        turntable_replace_connection_pool
      end

      def turntable_cluster
        turntable_clusters[turntable_cluster_name]
      end

      def turntable_pool_list
        turntable_clusters.values.map { |cluster| cluster.shards.map(&:connection_pool) }.flatten.uniq
      end

      def turntable_replace_connection_pool
        ch = connection_handler
        cp = ConnectionProxy.new(self, turntable_cluster)
        pp = PoolProxy.new(cp)

        self.connection_specification_name = "turntable_pool_proxy::#{name}"
        ch.owner_to_turntable_pool[connection_specification_name] = pp
      end

      def sequencer(sequencer_name, *args)
        class_attribute :turntable_sequencer_name
        class << self
          prepend ActiveRecordExt::Sequencer
        end

        self.turntable_sequencer_enabled = true
        self.turntable_sequencer_name = sequencer_name
      end

      def turntable_sequencer
        turntable_sequencers[turntable_sequencer_name]
      end

      def turntable_enabled?
        turntable_enabled
      end

      def sharding_condition_needed?
        turntable_enabled? && primary_key != turntable_shard_key.to_s
      end

      def sequencer_enabled?
        turntable_sequencer_enabled
      end

      def current_last_shard
        turntable_cluster.select_shard(current_sequence_value) if sequencer_enabled?
      end

      def with_shard(any_shard)
        shard = case any_shard
                when Numeric
                  turntable_cluster.shard_for(any_shard)
                when ActiveRecord::Base
                  turntable_cluster.shard_for(any_shard.send(any_shard.turntable_shard_key))
                else
                  shard_or_key
                end
        connection.with_shard(shard) { yield }
      end

      def with_slave
        connection.with_slave { yield }
      end

      def with_master
        connection.with_master { yield }
      end
    end

    delegate :shards_transaction, :turntable_cluster, to: :class

    # @return [ActiveRecord::Turntable::Shard] current shard for self
    def turntable_shard
      turntable_cluster.shard_for(self.send(turntable_shard_key))
    end

    # @see ActiveRecord::Turntable::ConnectionProxy#with_shard
    def with_shard(shard)
      self.class.connection.with_shard(shard) { yield }
    end
  end
end
