require "pact_broker/webhooks/render"
require "cgi"
require "pact_broker/domain/webhook_request"
require "pact_broker/string_refinements"

module PactBroker
  module Webhooks
    class WebhookRequestTemplate

      include PactBroker::Logging
      include PactBroker::Messages
      using PactBroker::StringRefinements

      HEADERS_TO_REDACT = [/authorization/i, /token/i]

      attr_accessor :method, :url, :body, :username, :password, :uuid
      attr_reader :headers

      # Reform gets confused by the :method method, as :method is a standard
      # Ruby method.
      alias_method :http_method, :method

      def initialize attributes = {}
        attributes.each do | (name, value) |
          instance_variable_set("@#{name}", value) if respond_to?(name)
        end
        @headers = Rack::Headers.new
        @headers.merge!(attributes[:headers]) if attributes[:headers]
      end

      def build(template_params, user_agent: nil, disable_ssl_verification: false, cert_store: nil)
        attributes = {
          method: http_method,
          url: build_url(template_params),
          headers: build_headers(template_params),
          username: build_string(username, template_params),
          password: build_string(password, template_params),
          uuid: uuid,
          body: build_body(template_params),
          user_agent: user_agent,
          disable_ssl_verification: disable_ssl_verification,
          cert_store: cert_store
        }
        PactBroker::Domain::WebhookRequest.new(attributes)
      end

      def description
        "#{http_method.upcase} #{URI(PactBroker::Webhooks::Render.render_with_placeholder(url)).host}"
      end

      def display_password
        password.nil? ? nil : (PactBroker::Webhooks::Render.includes_parameter?(password) ? password : "**********")
      end

      def redacted_headers
        headers.each_with_object({}) do | (name, value), new_headers |
          redact = HEADERS_TO_REDACT.any?{ | pattern | name =~ pattern }  && !PactBroker::Webhooks::Render.includes_parameter?(value)
          new_headers[name] = redact ? "**********" : value
        end
      end

      def headers= headers
        @headers.replace(headers)
      end

      def uses_parameter?(parameter_name)
        !!body_string&.include?("${" + parameter_name + "}")
      end

      def body_string
        String === body ? body : body&.to_json
      end

      def to_s
        "#{method.upcase} #{url}, username=#{username}, password=#{display_password}, headers=#{redacted_headers}, body=#{body}"
      end

      def template_parameters(scope = nil)
        body_template_parameters(scope) + url_template_parameters(scope) + header_template_parameters(scope) + credentials_template_parameters(scope)
      end

      def body_template_parameters(scope = nil)
        body_string.scan(parameter_pattern(scope)).flatten.uniq
      end

      def header_template_parameters(scope = nil)
        pattern = parameter_pattern(scope)
        headers.values.collect { |value| value.scan(pattern) }.flatten.uniq
      end

      def url_template_parameters(scope = nil)
        url.scan(parameter_pattern(scope)).flatten.uniq
      end

      def credentials_template_parameters(scope = nil)
        pattern = parameter_pattern(scope)
        [username, password].compact.collect do | credential |
          credential.scan(pattern)
        end.flatten.uniq
      end

      private

      def parameter_pattern(scope)
        scope ? /\${(#{scope}\.[a-zA-z]+)}/ : /\${([a-zA-z]+\.[a-zA-z]+)}/
      end

      def build_url(template_params)
        URI(PactBroker::Webhooks::Render.call(url, template_params){ | value | CGI::escape(value) if !value.nil? } ).to_s
      end

      def build_body(template_params)
        body_string = String === body ? body : body.to_json
        build_string(body_string, template_params)
      end

      def build_headers(template_params)
        headers.each_with_object(Rack::Headers.new) do | (key, value), new_headers |
          new_headers[key] = build_string(value, template_params)
        end
      end

      def build_string(string, template_params)
        return string if string.nil? || string.blank?
        PactBroker::Webhooks::Render.call(string, template_params)
      end
    end
  end
end
