# typed: true
class HttpClientMockHelpers
  include HasColorLogger
  extend FactoryBot::Syntax::Methods
  extend RSpec::Mocks::ExampleMethods
  extend RSpec::Matchers

  def self.init_from_manifest(http_client_mock, manifest_directory_path)
    manifest =
      YAML.safe_load(
        File.read(Rails.root.join(manifest_directory_path, "manifest.yml")),
      )

    requests =
      manifest.map do |request_info|
        {
          uri: request_info["url"],
          content_type:
            (
              if request_info["url"].include?(".json")
                "application/json"
              else
                "text/html"
              end
            ),
          contents:
            File.read(
              Rails.root.join(manifest_directory_path, request_info["file"]),
            ),
          status_code: request_info["status"],
          method: request_info["method"],
          caused_by_entry: :any,
          requested_at: request_info["requested_at"],
        }
      end

    HttpClientMockHelpers.init_http_client_mock(
      http_client_mock,
      requests,
      any_order: true,
    )
  end

  def self.init_http_client_mock(http_client_mock, requests, any_order: false)
    if any_order
      init_any_order(http_client_mock, requests)
    else
      init_ordered(http_client_mock, requests)
    end
  end

  def self.init_ordered(http_client_mock, requests)
    log_entries = []

    requests.each do |request|
      sha256 = Digest::SHA256.digest(request[:contents])
      log_entry =
        build(
          :http_log_entry,
          uri: request[:uri],
          verb: :get,
          content_type: request[:content_type],
          status_code: request[:status_code] || 200,
          performed_by: "direct",
          response_time_ms: rand(20..100),
          requested_at: request[:requested_at] || Time.now,
          request_headers: build(:http_log_entry_header),
          response_headers: build(:http_log_entry_header),
          response:
            BlobFile.find_by(sha256: sha256) ||
              build(
                :blob_file,
                content_type: request[:content_type],
                contents: request[:contents],
              ),
        )
      log_entry.save!
      log_entries << log_entry
      caused_by_entry = nil
      if request[:caused_by_entry_idx]
        caused_by_entry = log_entries[request[:caused_by_entry_idx]]
      elsif request[:caused_by_entry] == :any
        caused_by_entry = :any
      elsif request[:caused_by_entry]
        caused_by_entry = request[:caused_by_entry]
      end

      method = request[:method] || :get
      expect(http_client_mock).to(
        receive(method).with(
          log_entry.uri.to_s,
          http_client_opts_with(
            caused_by_entry: caused_by_entry,
            use_http_cache: request[:use_http_cache],
          ),
        ) do |uri, opts|
          logger.info "[mock http client] [#{method}] [#{uri}] [#{opts.inspect.truncate(80)}]"
          Scraper::HttpClient::Response.new(
            status_code: log_entry.status_code,
            body: log_entry.response.content_bytes,
            log_entry: log_entry,
          )
        end,
      )
    end

    log_entries
  end

  def self.init_any_order(http_client_mock, requests)
    log_entries_by_uri =
      requests
        .map do |request|
          sha256 = Digest::SHA256.digest(request[:contents])
          log_entry =
            create(
              :http_log_entry,
              uri: request[:uri],
              verb: :get,
              content_type: request[:content_type],
              status_code: request[:status_code] || 200,
              performed_by: "direct",
              response_time_ms: rand(20..100),
              response:
                BlobFile.find_by(sha256: sha256) ||
                  build(
                    :blob_file,
                    content_type: request[:content_type],
                    contents: request[:contents],
                  ),
            )
          [request[:uri], log_entry]
        end
        .to_h

    allow(http_client_mock).to receive(:get) do |uri, opts|
      log_entry = log_entries_by_uri[uri]
      expect(log_entry).not_to(
        be_nil,
        "no log entry found for uri: #{uri}\nValid uris: \n#{log_entries_by_uri.keys.join("\n")}",
      )
      logger.info "[mock http client] [get] [#{uri}] [#{opts.inspect.truncate(80)}]"
      Scraper::HttpClient::Response.new(
        status_code: log_entry.status_code,
        body: log_entry.response.content_bytes,
        log_entry: log_entry,
      )
    end

    log_entries_by_uri.values
  end
end
