module Deferring
  module Matchers

    class ArQuery  #:nodoc:
      cattr_accessor :executed

      @@recording_queries = false
      def self.recording_queries?
        @@recording_queries
      end

      def initialize(expected, &block)
        @expected = expected
        @block = block
      end

      def matches?(given_proc)
        @eval_block = false
        @eval_error = nil
        ArQuery.executed = []
        @@recording_queries = true

        given_proc.call

        if @expected.is_a?(Integer)
          @actual = ArQuery.executed.length
          @matched = @actual == @expected
        else
          @actual = ArQuery.executed.detect { |sql| @expected === sql }
          @matched = !@actual.nil?
        end

        eval_block if @block && @matched && !negative_expectation?

      ensure
        ArQuery.executed = nil
        @@recording_queries = false
        return @matched && @eval_error.nil?
      end

      def eval_block
        @eval_block = true
        begin
          @block[ArQuery.executed]
        rescue => err
          @eval_error = err
        end
      end

      def supports_block_expectations?
        true
      end

      def failure_message
        if @eval_error
          @eval_error.message
        elsif @expected.is_a?(Integer)
          "expected #{@expected}, got #{@actual}"
        else
          "expected to execute a query with pattern #{@expected.inspect}, but it wasn't"
        end
      end

      def failure_message_when_negated
        if @expected.is_a?(Integer)
          "did not expect #{@expected}"
        else
          "did not expect to execute a query with pattern #{@expected.inspect}, but it was executed"
        end
      end

      def description
        if @expected.is_a?(Integer)
          @expected == 1 ? 'execute 1 query' : "execute #{@expected} queries"
        else
          "execute query with pattern #{@expected.inspect}"
        end
      end

      def negative_expectation?
        @negative_expectation ||= !caller.first(3).find { |s| s =~ /should_not/ }.nil?
      end
    end

    def query(expected = 1, &block)
      ArQuery.new(expected, &block)
    end
  end
end

# For Rails 6.0
module ActiveRecord
  module ConnectionAdapters
    module SQLite3
      module DatabaseStatements
        [:exec_query, :exec, :execute].each do |method|
          if respond_to?(method)
            define_method("#{method}_with_query_record") do |sql, *args|
              Deferring::Matchers::ArQuery.executed << sql if Deferring::Matchers::ArQuery.recording_queries?
              send("#{method}_without_query_record", sql, *args)
            end

            alias_method :"#{method}_without_query_record", method
            alias_method method, :"#{method}_with_query_record"
          end
        end
      end
    end
  end
end

# For previous versions of Rails
module ActiveRecord
  module ConnectionAdapters
    class SQLite3Adapter
      [:exec_query, :exec, :execute].each do |method|
        define_method("#{method}_with_query_record") do |sql, *args|
          Deferring::Matchers::ArQuery.executed << sql if Deferring::Matchers::ArQuery.recording_queries?
          send("#{method}_without_query_record", sql, *args)
        end

        alias_method :"#{method}_without_query_record", method
        alias_method method, :"#{method}_with_query_record"
      end
    end
  end
end