# frozen_string_literal: true

class FetchOEmbedService
  ENDPOINT_CACHE_EXPIRES_IN = 24.hours.freeze
  URL_REGEX                 = %r{(=(https?(%3A|:)(//|%2F%2F)))([^&]*)}i

  attr_reader :url, :options, :format, :endpoint_url

  def call(url, options = {})
    @url     = url
    @options = options

    if @options[:cached_endpoint]
      parse_cached_endpoint!
    else
      discover_endpoint!
    end

    fetch!
  end

  private

  def discover_endpoint!
    return if html.nil?

    @format = @options[:format]
    page    = Nokogiri::HTML5(html)

    if @format.nil? || @format == :json
      @endpoint_url ||= page.at_xpath('//link[@type="application/json+oembed"]|//link[@type="text/json+oembed"]')&.attribute('href')&.value
      @format       ||= :json if @endpoint_url
    end

    if @format.nil? || @format == :xml
      @endpoint_url ||= page.at_xpath('//link[@type="text/xml+oembed"]')&.attribute('href')&.value
      @format       ||= :xml if @endpoint_url
    end

    return if @endpoint_url.blank?

    @endpoint_url = begin
      base_url = Addressable::URI.parse(@url)

      # If the OEmbed endpoint is given as http but the URL we opened
      # was served over https, we can assume OEmbed will be available
      # through https as well

      (base_url + @endpoint_url).tap do |absolute_url|
        absolute_url.scheme = base_url.scheme if base_url.scheme == 'https'
      end.to_s
    end

    cache_endpoint!
  rescue Addressable::URI::InvalidURIError
    @endpoint_url = nil
  end

  def parse_cached_endpoint!
    cached = @options[:cached_endpoint]

    return if cached[:endpoint].nil? || cached[:format].nil?

    @endpoint_url = Addressable::Template.new(cached[:endpoint]).expand(url: @url).to_s
    @format       = cached[:format]
  end

  def cache_endpoint!
    return unless URL_REGEX.match?(@endpoint_url)

    url_domain = Addressable::URI.parse(@url).normalized_host

    endpoint_hash = {
      endpoint: @endpoint_url.gsub(URL_REGEX, '={url}'),
      format: @format,
    }

    Rails.cache.write("oembed_endpoint:#{url_domain}", endpoint_hash, expires_in: ENDPOINT_CACHE_EXPIRES_IN)
  end

  def fetch!
    return if @endpoint_url.blank?

    body = Request.new(:get, @endpoint_url).perform do |res|
      res.code == 200 ? res.body_with_limit : nil
    end

    validate(parse_for_format(body)) if body.present?
  rescue JSON::ParserError, Ox::ParseError
    nil
  end

  def parse_for_format(body)
    case @format
    when :json
      JSON.parse(body)&.with_indifferent_access
    when :xml
      Ox.load(body, mode: :hash_no_attrs)&.with_indifferent_access&.dig(:oembed)
    end
  end

  def validate(oembed)
    oembed if oembed.present? && oembed[:version].to_s == '1.0' && oembed[:type].present?
  end

  def html
    return @html if defined?(@html)

    @html = @options[:html] || Request.new(:get, @url).add_headers('Accept' => 'text/html').perform do |res|
      res.code != 200 || res.mime_type != 'text/html' ? nil : res.body_with_limit
    end
  end
end
