require 'pact/shared/dsl'
require 'pact/provider/world'
require 'pact/pact_broker/fetch_pact_uris_for_verification'
require 'pact/errors'
require 'pact/utils/string'

module Pact
  module Provider
    module Configuration
      class PactVerificationFromBroker

        extend Pact::DSL

        # If user declares a variable with the same name as one of these attributes
        # in parent scope, it will clash with these ones,
        # so put an underscore in front of the name to be safer.

        attr_accessor :_provider_name, :_pact_broker_base_url, :_consumer_version_tags, :_provider_version_branch, :_provider_version_tags, :_basic_auth_options, :_enable_pending, :_include_wip_pacts_since, :_verbose, :_consumer_version_selectors, :_fail_if_no_pacts_found

        def initialize(provider_name, provider_version_branch, provider_version_tags)
          @_provider_name = provider_name
          @_provider_version_branch = provider_version_branch
          @_provider_version_tags = provider_version_tags
          @_consumer_version_tags = []
          @_consumer_version_selectors = []
          @_enable_pending = false
          @_include_wip_pacts_since = nil
          @_verbose = false
          @_fail_if_no_pacts_found = true # CLI defaults to false, unfortunately for consistency
        end

        dsl do
          def pact_broker_base_url pact_broker_base_url, basic_auth_options = {}
            self._pact_broker_base_url = pact_broker_base_url
            self._basic_auth_options = basic_auth_options
          end

          def consumer_version_tags consumer_version_tags
            self._consumer_version_tags = *consumer_version_tags
          end

          def consumer_version_selectors consumer_version_selectors
            self._consumer_version_selectors = *consumer_version_selectors
          end

          def enable_pending enable_pending
            self._enable_pending = enable_pending
          end

          # Underlying code defaults to true if not specified
          def fail_if_no_pacts_found fail_if_no_pacts_found
            self._fail_if_no_pacts_found = fail_if_no_pacts_found
          end

          def include_wip_pacts_since since
            self._include_wip_pacts_since = if since.respond_to?(:xmlschema)
              since.xmlschema
            else
              since
            end
          end

          def verbose verbose
            self._verbose = verbose
          end
        end

        def finalize
          validate
          create_pact_verification
        end

        private

        def create_pact_verification
          fetch_pacts = Pact::PactBroker::FetchPactURIsForVerification.new(
            _provider_name,
            consumer_version_selectors,
            _provider_version_branch,
            _provider_version_tags,
            _pact_broker_base_url,
            _basic_auth_options.merge(verbose: _verbose),
            { include_pending_status: _enable_pending, include_wip_pacts_since: _include_wip_pacts_since, fail_if_no_pacts_found: _fail_if_no_pacts_found }
          )

          Pact.provider_world.add_pact_uri_source fetch_pacts
        end

        def consumer_version_selectors
          convert_tags_to_selectors + convert_consumer_version_selectors
        end

        def convert_tags_to_selectors
          _consumer_version_tags.collect do | tag |
            if tag.is_a?(Hash)
              {
                tag: tag.fetch(:name),
                latest: !tag[:all],
                fallbackTag: tag[:fallback]
              }
            elsif tag.is_a?(String)
              {
                tag: tag,
                latest: true
              }
            else
              raise Pact::Error.new("The value supplied for consumer_version_tags must be a String or a Hash. Found #{tag.class}")
            end
          end
        end

        def convert_consumer_version_selectors
          _consumer_version_selectors.collect do | selector |
            selector.each_with_object({}) do | (key, value), new_selector |
              new_selector[Pact::Utils::String.camelcase(key.to_s).to_sym] = value
            end
          end
        end

        def validate
          raise Pact::Error.new("Please provide a pact_broker_base_url from which to retrieve the pacts") unless _pact_broker_base_url
        end
      end
    end
  end
end