require 'bearcat'

# Bearcat RSpec Helpers
# Can be included in your spec_helper.rb file by using:
# - `require 'bearcat/spec_helpers'`
# And
# - `config.include Bearcat::SpecHelpers`
# This helper requires `gem 'method_source'` in your test group
module Bearcat::SpecHelpers
  extend ActiveSupport::Concern

  SOURCE_REGEX = /(?<method>get|post|delete|put)\((?<quote>\\?('|"))(?<url>.*)\k<quote>/

  # Helper method to Stub Bearcat requests.
  # Automagically parses the Bearcat method source to determine the correct URL to stub.
  # Accepts optional keyword parameters to interpolate specific values into the URL.
  # Returns a mostly-normal Webmock stub (:to_return has been overridden to allow :body to be set to a Hash)
  def stub_bearcat(endpoint, prefix: nil, method: :auto, **kwargs)
    cfg = _bearcat_resolve_config(endpoint, kwargs, method: method)

    url = Regexp.escape(_bearcat_resolve_prefix(prefix)) + cfg[:url]
    url += "?" if url.end_with?('/')
    stub = stub_request(cfg[:method], Regexp.new(url))

    # Override the to_return method to accept a Hash as body:
    stub.define_singleton_method(:to_return, ->(*resps, &blk) {
      if blk
        super do |*args|
          resp = blk.call(*args)
          resp[:headers] ||= {}
          resp[:headers]["Content-Type"] ||= "application/json"
          resp[:body] = resp[:body].to_json
          resp
        end
      else
        resps.map do |resp|
          resp[:headers] ||= {}
          resp[:headers]["Content-Type"] ||= "application/json"
          resp[:body] = resp[:body].to_json
        end
        super(*resps)
      end
    })

    stub
  end

  private

  def _bearcat_resolve_config(endpoint, url_context, method: :auto)
    case endpoint
    when Symbol
      if (cfg = Bearcat::Client.registered_endpoints[endpoint]).present?
        bits = []
        url = cfg[:url]
        lend = 0
        url.scan(/:(?<key>\w+)/) do |key|
          m = Regexp.last_match
          between = url[lend..m.begin(0)-1]
          bits << between if between.present? && (m.begin(0) > lend)
          lend = m.end(0)
          bits << (url_context[m[:key].to_sym] || /\w+/)
        end
        between = url[lend..-1]
        bits << between if between.present?

        url = bits.map do |bit|
          next bit.source if bit.is_a?(Regexp)
          bit = bit.canvas_id if bit.respond_to?(:canvas_id)
          Regexp.escape(bit.to_s)
        end.join

        { method: method == :auto ? cfg[:method] : method, url: url }
      else
        ruby_method = Bearcat::Client.instance_method(endpoint)
        match = SOURCE_REGEX.match(ruby_method.source)
        bits = []
        url = match[:url].gsub(/\/$/, '')
        lend = 0
        url.scan(/#\{(?<key>.*?)\}/) do |key|
          m = Regexp.last_match
          between = url[lend..m.begin(0)-1]
          bits << between if between.present? && (m.begin(0) > lend)
          lend = m.end(0)
          bits << (url_context[m[:key].to_sym] || /\w+/)
        end
        between = url[lend..-1]
        bits << between if between.present?

        url = bits.map do |bit|
          next bit.source if bit.is_a?(Regexp)
          bit = bit.canvas_id if bit.respond_to?(:canvas_id)
          Regexp.escape(bit.to_s)
        end.join

        { method: method == :auto ? (match ? match[:method].to_sym : :get) : method, url: url }
      end
    when String
      raise "Cannot use method :auto when passing string endpoint" if method == :auto
      { method: method, url: Regexp.escape(endpoint) }
    when Regexp
      raise "Cannot use method :auto when passing regex endpoint" if method == :auto
      { method: method, url: Regexp.escape(endpoint) }
    end
  end

  def _bearcat_resolve_prefix(prefix)
    if prefix == true
      prefix = canvas_api_client if defined? canvas_api_client
      prefix = canvas_sync_client if defined? canvas_sync_client
    end
    prefix = case prefix
    when nil
      ''
    when false
      ''
    when true
      raise "stub_bearcat() prefix: set to true, but neither canvas_(sync|api)_client are defined"
    when Bearcat::Client
      prefix.prefix
    when String
      prefix
    end
  end
end