# frozen_string_literal: true

require 'switch_point/error'
require 'switch_point/proxy_repository'

module SwitchPoint
  module Model
    def self.included(model)
      super
      model.singleton_class.class_eval do
        include ClassMethods
        prepend MonkeyPatch
      end
    end

    def with_readonly(&block)
      self.class.with_readonly(&block)
    end

    def with_writable(&block)
      self.class.with_writable(&block)
    end

    def transaction_with(*models, &block)
      self.class.transaction_with(*models, &block)
    end

    module ClassMethods
      def with_readonly(&block)
        if switch_point_proxy
          switch_point_proxy.with_readonly(&block)
        else
          raise UnconfiguredError.new("#{name} isn't configured to use switch_point")
        end
      end

      def with_writable(&block)
        if switch_point_proxy
          switch_point_proxy.with_writable(&block)
        else
          raise UnconfiguredError.new("#{name} isn't configured to use switch_point")
        end
      end

      def use_switch_point(name)
        assert_existing_switch_point!(name)
        @switch_point_name = name
      end

      def switch_point_proxy
        if defined?(@switch_point_name)
          ProxyRepository.checkout(@switch_point_name)
        elsif self == ActiveRecord::Base
          nil
        else
          superclass.switch_point_proxy
        end
      end

      def transaction_with(*models, &block)
        unless can_transaction_with?(*models)
          raise Error.new("switch_point's model names must be consistent")
        end

        with_writable do
          transaction(&block)
        end
      end

      private

      def assert_existing_switch_point!(name)
        SwitchPoint.config.fetch(name)
      end

      def can_transaction_with?(*models)
        writable_switch_points = [self, *models].map do |model|
          if model.instance_variable_defined?(:@switch_point_name)
            SwitchPoint.config.model_name(
              model.instance_variable_get(:@switch_point_name),
              :writable
            )
          end
        end

        writable_switch_points.uniq.size == 1
      end
    end

    module MonkeyPatch
      def connection
        if switch_point_proxy
          switch_point_proxy.connection
        else
          super
        end
      end

      def cache(&block)
        if switch_point_proxy
          switch_point_proxy.cache(&block)
        else
          super
        end
      end

      def uncached(&block)
        if switch_point_proxy
          switch_point_proxy.uncached(&block)
        else
          super
        end
      end
    end
  end
end
