module Matterhorn
  class FayeSignMiddleware
    META_CHANNEL_REGEX = %r{^(?:/meta/(?:handshake|connect|subscribe|disconnect))$}
    VALID_CHANNEL_REGEX = %r{^(/_debug|/queue/[a-zA-Z0-9_-]+/[a-zA-Z0-9_-]+)$}

    def incoming(message, callback)
      @message = munge_hash_keys(message)
      message.delete('ext') # don't share extension data

      # for subscriptions, validate channel names to Matterhorn projects
      if @message[:channel] == '/meta/subscribe'
        @message[:error] = "Invalid subscription requested for #{@message[:subscription]}" unless valid_channel?(@message[:subscription])
        return callback.call(message)
      end

      # allow all other meta requests
      return callback.call(message) if @message[:channel] =~ META_CHANNEL_REGEX

      # for non-meta requests, validate the channel name and validate signed requests
      message['error'] = 'Invalid channel' unless valid_channel?(@message[:channel])
      message['error'] ||= 'Invalid signature' unless valid_signature?

      callback.call(message)
    end

    def project_name(channel)
      md = VALID_CHANNEL_REGEX.match(channel)
      return nil if md.nil?
      requested_project = md[1]
      requested_project.sub(%r{^/(queue/)?}, '')
    end

    def valid_channel?(channel)
      return true if channel == '/_debug' && ['development', 'shadow'].include?(RACK_ENV)
      pn = project_name(channel)
      !pn.nil? && PROJECTS[:projects].key?(pn)
    end

    def valid_signature?
      Matterhorn::FayeSign.verify(@message)
    end

    def munge_hash_keys(message)
      final_hash = {}
      message.each do |key, value|
        if value.is_a?(Hash)
          final_hash[key.to_sym] = munge_hash_keys(value)
        else
          final_hash[key.to_sym] = value
        end
      end
      final_hash
    end
  end

  class FayeSign
    class << self
      def payload(message)
        return nil if message[:channel].nil? || message[:data].nil?
        "#{message[:channel]}::#{message[:data].to_json}"
      end

      def sign(message)
        return false if (p = payload(message)).nil?
        Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha256'), MATTERHORN_SECRET_KEY, p.encode('UTF-8'))).strip
      end

      def verify(message)
        message.dig(:ext, :signature) == sign(message)
      end
    end
  end

  class FayeMessage
    class << self
      def send(message)
        message[:ext] ||= {}
        message[:ext][:signature] = Matterhorn::FayeSign.sign(message)
        Net::HTTP.post_form(URI.parse("#{SITE_URL}/faye"), message: message.to_json)
      end
    end
  end
end
