module Preview
  class ThumbnailFetcher
    class GenerationFailed < StandardError; end
    RASTER_IMAGE_CONTENT_TYPES = [
      'image/jpeg', 'image/png', 'image/gif', 'image/webp'
    ].freeze
    HTML_MAX_BYTES = 1.megabyte
    NICONICO_XML_MAX_BYTES = 256.kilobytes

    def self.fetch(raw_url)
      uri, = UrlSafety.validate(raw_url)

      known_url = KnownSiteExtractor.thumbnail_url(uri)
      image = fetch_image_or_nil(known_url) if known_url
      return image if image

      niconico_url = niconico_thumbnail_url(uri)
      image = fetch_image_or_nil(niconico_url) if niconico_url
      return image if image

      page = HttpFetcher.fetch(uri.to_s, max_bytes: HTML_MAX_BYTES)
      metadata = HtmlMetadataExtractor.extract(page)
      raise GenerationFailed, 'サムネール画像が見つかりませんでした．' if metadata[:image_url].blank?

      fetch_image!(metadata[:image_url])
    end

    def self.fetch_image_response(raw_url)
      uri, = UrlSafety.validate(raw_url)
      response = HttpFetcher.fetch(uri.to_s)
      unless Post.remote_thumbnail_image_bytes?(
                 response.body,
                 content_type: response.content_type)
        raise GenerationFailed, 'サムネール画像が見つかりませんでした．'
      end

      response
    rescue HttpFetcher::FetchTimeout
      raise
    rescue HttpFetcher::ResponseTooLarge
      raise
    rescue HttpFetcher::FetchFailed
      raise GenerationFailed, 'サムネール画像を取得できませんでした．'
    end

    def self.title(raw_url)
      uri, = UrlSafety.validate(raw_url)
      HtmlMetadataExtractor.extract(
          HttpFetcher.fetch(uri.to_s, max_bytes: HTML_MAX_BYTES))[:title]
    end

    def self.fetch_image_or_nil(url)
      return nil if url.blank?

      response = HttpFetcher.fetch(url)
      return nil unless Post.remote_thumbnail_image_bytes?(
                            response.body,
                            content_type: response.content_type)

      response.body
    rescue HttpFetcher::FetchTimeout
      raise
    rescue HttpFetcher::FetchFailed
      nil
    end

    def self.fetch_image!(url)
      fetch_image_response(url).body
    end

    def self.niconico_thumbnail_url(uri)
      video_id = KnownSiteExtractor.niconico_video_id(uri)
      return nil if video_id.blank?

      response = HttpFetcher.fetch("https://ext.nicovideo.jp/api/getthumbinfo/#{ video_id }",
                                   max_bytes: NICONICO_XML_MAX_BYTES)
      xml = Nokogiri::XML(response.body)
      return nil unless xml.at_xpath('/nicovideo_thumb_response/@status')&.value == 'ok'

      xml.at_xpath('//thumbnail_url')&.text&.strip.presence
    rescue HttpFetcher::FetchFailed, HttpFetcher::FetchTimeout => e
      payload = {
          url: uri.to_s,
          video_id:,
          error: e.class.name,
          message: e.message }
      Rails.logger.info("preview_niconico_getthumbinfo_fallback #{ payload.to_json }")
      nil
    rescue Nokogiri::XML::SyntaxError => e
      payload = {
          url: uri.to_s,
          video_id:,
          error: e.class.name,
          message: e.message }
      Rails.logger.info("preview_niconico_getthumbinfo_fallback #{ payload.to_json }")
      nil
    end

    private_class_method :fetch_image_or_nil, :fetch_image!,
                         :niconico_thumbnail_url
  end
end
