# frozen_string_literal: true

require 'erb'
require 'httparty'
require 'pact_broker/client/error'
require 'cgi'

module PactBroker
  module Client

    class Error < StandardError; end

    module UrlHelpers
      def encode_param param
        ERB::Util.url_encode param
      end

      def encode_query_param param
        CGI::escape param
      end
    end

    module StringToSymbol

      #Only works for one level, not recursive!
      def string_keys_to_symbols hash
        hash.keys.each_with_object({}) do | key, new_hash |
          new_hash[key.to_sym] = hash[key]
        end
      end

    end

    class BaseClient
      ERROR_CODE_MAPPING = {
        401 => "Authentication failed",
        403 => "Authorization failed (insufficient permissions)",
        409 => "Potential duplicate pacticipants"
      }.freeze

      include UrlHelpers
      include HTTParty
      include StringToSymbol

      attr_reader :base_url, :client_options

      def initialize options
        @base_url = options[:base_url]
        @client_options = options[:client_options] || {}
        @verbose = @client_options[:verbose]
        self.class.base_uri base_url
        self.class.debug_output($stderr) if verbose?
        self.class.basic_auth(client_options[:basic_auth][:username], client_options[:basic_auth][:password]) if client_options[:basic_auth]
        self.class.headers('Authorization' => "Bearer #{client_options[:token]}") if client_options[:token]
        self.class.ssl_ca_file(ENV['SSL_CERT_FILE']) if ENV['SSL_CERT_FILE'] && ENV['SSL_CERT_FILE'] != ''
        self.class.ssl_ca_path(ENV['SSL_CERT_DIR']) if ENV['SSL_CERT_DIR'] && ENV['SSL_CERT_DIR'] != ''
        @default_options = {}
        @default_options[:verify] = false if (ENV['PACT_DISABLE_SSL_VERIFICATION'] == 'true' || ENV['PACT_BROKER_DISABLE_SSL_VERIFICATION'] == 'true')
      end

      def default_request_headers
        {'Accept' => 'application/hal+json, application/json'}
      end

      def default_get_headers
        default_request_headers
      end

      def default_patch_headers
        default_request_headers.merge('Content-Type' => 'application/json')
      end

      def default_put_headers
        default_request_headers.merge('Content-Type' => 'application/json')
      end

      def handle_response response
        if response.success?
          yield response
        elsif response.code == 404
          nil
        elsif ERROR_CODE_MAPPING.key?(response.code)
          message = ERROR_CODE_MAPPING.fetch(response.code)
          if response.body && response.body.size > 0
            message = message + ": #{response.body}"
          end
          raise Error.new(message)
        else
          error_message = nil
          begin
            errors = JSON.parse(response.body)['errors']
            error_message = if errors.is_a?(Array)
              errors.join("\n")
            elsif errors.is_a?(Hash)
              errors.collect{ |key, value| "#{key}: #{value}" }.join("\n")
            else
              response.body
            end
          rescue
            raise Error.new("status=#{response.code} #{response.body}")
          end
          raise Error.new(error_message)
        end
      end

      def patch url, options
        self.class.patch(url, @default_options.merge(options.merge(body: options[:body].to_json)))
      end

      def put url, options = {}, &block
        self.class.put(url, @default_options.merge(options), &block)
      end

      def get url, options = {}, &block
        self.class.get(url, @default_options.merge(options), &block)
      end

      def url_for_relation relation_name, params
        handle_response(get("/", headers: default_get_headers)) do | response |
          relation = (JSON.parse(response.body)['_links'] || {})[relation_name]
          if relation
            url = relation['href']
            params.each do | (key, value) |
              url = url.gsub("{#{key}}", encode_param(value))
            end
            url
          else
            raise PactBroker::Client::RelationNotFound.new("Could not find relation #{relation_name} in index resource. Try upgrading your Pact Broker as the feature you require may not exist in your version. If you are using PactFlow, you may not have the permissions required for this action.")
          end
        end
      end

      def verbose?
        @verbose
      end
    end
  end
end
