# frozen_string_literal: true

require 'digest/sha1'

module MarketoApi
  class Middleware::CachingStrategy < Faraday::HttpCache::Strategies::BaseStrategy
    def write(request, response)
      oauth_token = get_oauth_token(request)
      key = cache_key_for(request.url, oauth_token)
      entry = serialize_entry(request.serializable_hash, response.serializable_hash)
      entries = cache.read(key) || []
      entries = entries.dup if entries.frozen?
      entries.reject! do |(cached_request, cached_response)|
        response_matches?(request, deserialize_object(cached_request), deserialize_object(cached_response))
      end

      entries << entry

      cache.write(key, entries)
    rescue ::Encoding::UndefinedConversionError => e
      warn "Response could not be serialized: #{e.message}. Try using Marshal to serialize."
      raise e
    end

    def read(request)
      oauth_token = get_oauth_token(request)
      cache_key = cache_key_for(request.url, oauth_token)
      entries = cache.read(cache_key)
      response = lookup_response(request, entries)
      return nil unless response

      Faraday::HttpCache::Response.new(response)
    end

    def delete(request, url)
      oauth_token = get_oauth_token(request)
      cache_key = cache_key_for(url, oauth_token)
      cache.delete(cache_key)
    end

    # We don't want to cache requests for different clients, so append the
    # oauth token to the cache key.
    def cache_key_for(url, oauth_token)
      Digest::SHA1.hexdigest("#{@cache_salt}#{url}") + Digest::SHA1.hexdigest(oauth_token)
    end

    private

    def lookup_response(request, entries)
      if entries
        entries = entries.map { |entry| deserialize_entry(*entry) }
        _, response = entries.find { |req, res| response_matches?(request, req, res) }
        response
      end
    end

    def response_matches?(request, cached_request, cached_response)
      request.method.to_s == cached_request[:method].to_s &&
        vary_matches?(cached_response, request, cached_request)
    end

    def vary_matches?(cached_response, request, cached_request)
      headers = Faraday::Utils::Headers.new(cached_response[:response_headers])
      vary = headers['Vary'].to_s

      vary.empty? || (vary != '*' && vary.split(/[\s,]+/).all? do |header|
        request.headers[header] == cached_request[:headers][header]
      end)
    end

    def get_oauth_token(request)
      request.headers[MarketoApi::Middleware::Authorization::AUTH_HEADER]
    end
  end
end