module DiasporaFederation
  module Discovery
    # This class contains the logic to fetch all data for the given diaspora* ID.
    class Discovery
      include DiasporaFederation::Logging

      # @return [String] the diaspora* ID of the account
      attr_reader :diaspora_id

      # Creates a discovery class for the diaspora* ID
      # @param [String] diaspora_id the diaspora* ID to discover
      def initialize(diaspora_id)
        @diaspora_id = clean_diaspora_id(diaspora_id)
      end

      # Fetches all metadata for the account and saves it via callback
      # @return [Person]
      # @raise [DiscoveryError] if something with the discovery failed
      def fetch_and_save
        logger.info "Fetch data for #{diaspora_id}"

        validate_diaspora_id

        DiasporaFederation.callbacks.trigger(:save_person_after_webfinger, person)
        logger.info "successfully webfingered #{diaspora_id}"
        person
      rescue DiscoveryError
        raise # simply re-raise DiscoveryError
      rescue => e # rubocop:disable Style/RescueStandardError
        raise DiscoveryError, "Failed discovery for #{diaspora_id}: #{e.class}: #{e.message}"
      end

      private

      def validate_diaspora_id
        # Validates if the diaspora* ID matches the diaspora* ID in the webfinger response
        return if diaspora_id == clean_diaspora_id(webfinger.acct_uri)
        raise DiscoveryError, "diaspora* ID does not match: Wanted #{diaspora_id} but got" \
                              " #{clean_diaspora_id(webfinger.acct_uri)}"
      end

      def clean_diaspora_id(diaspora_id)
        diaspora_id.strip.sub("acct:", "").to_s.downcase
      end

      def get(url, http_fallback=false)
        logger.info "Fetching #{url} for #{diaspora_id}"
        response = HttpClient.get(url)
        raise "Failed to fetch #{url}: #{response.status}" unless response.success?
        response.body
      rescue => e # rubocop:disable Style/RescueStandardError
        unless http_fallback && url.start_with?("https://")
          raise DiscoveryError, "Failed to fetch #{url} for #{diaspora_id}: #{e.class}: #{e.message}"
        end

        logger.warn "Retry with http: #{url} for #{diaspora_id}: #{e.class}: #{e.message}"
        url.sub!("https://", "http://")
        retry
      end

      def domain
        @domain ||= diaspora_id.split("@")[1]
      end

      def acct_parameter
        "acct:#{diaspora_id}"
      end

      def legacy_webfinger_url_from_host_meta
        # This tries the xrd url with https first, then falls back to http.
        host_meta = HostMeta.from_xml(get("https://#{domain}/.well-known/host-meta", true))
        host_meta.webfinger_template_url.gsub("{uri}", acct_parameter)
      end

      def webfinger
        return @webfinger if @webfinger
        webfinger_url = "https://#{domain}/.well-known/webfinger?resource=#{acct_parameter}"

        # This tries the WebFinger URL with https first, then falls back to http if webfinger_http_fallback is enabled.
        @webfinger = WebFinger.from_json(get(webfinger_url, DiasporaFederation.webfinger_http_fallback))
      rescue => e # rubocop:disable Style/RescueStandardError
        logger.warn "WebFinger failed, retrying with legacy WebFinger for #{diaspora_id}: #{e.class}: #{e.message}"
        @webfinger = WebFinger.from_xml(get(legacy_webfinger_url_from_host_meta))
      end

      def hcard
        @hcard ||= HCard.from_html(get(webfinger.hcard_url))
      end

      def person
        @person ||= Entities::Person.new(
          guid:         hcard.guid,
          diaspora_id:  diaspora_id,
          url:          webfinger.seed_url,
          exported_key: hcard.public_key,
          profile:      profile
        )
      end

      def profile
        Entities::Profile.new(
          diaspora_id:      diaspora_id,
          first_name:       hcard.first_name,
          last_name:        hcard.last_name,
          image_url:        hcard.photo_large_url,
          image_url_medium: hcard.photo_medium_url,
          image_url_small:  hcard.photo_small_url,
          searchable:       hcard.searchable
        )
      end
    end
  end
end
