# frozen_string_literal: true

module Gitlab
  module Pages
    class UrlBuilder
      attr_reader :project_namespace

      ALLOWED_ARTIFACT_EXTENSIONS = %w[.html .htm .txt .json .xml .log].freeze
      ARTIFACT_URL = "%{host}/-/%{project_path}/-/jobs/%{job_id}/artifacts/%{artifact_path}"

      def initialize(project, options = nil)
        @project = project
        namespace, _, @project_path = project.full_path.partition('/')
        @project_namespace = namespace.downcase
        @options = options || {}
      end

      def pages_url
        default_url
          .to_s
      end

      def unique_host
        return unless unique_domain_enabled?
        return if namespace_in_path?

        hostname
      end

      # If the project path is the same as host, we serve it as group/user page.
      #
      # e.g. For Pages external url `example.io`,
      #      `acmecorp/acmecorp.example.io` project will publish to `http(s)://acmecorp.example.io`
      # See https://docs.gitlab.com/ee/user/project/pages/getting_started_part_one.html#user-and-group-website-examples.
      def is_namespace_homepage? # rubocop:disable Naming/PredicateName -- namespace_homepage is not an
        # adjective, so adding "is_" improves understandability
        project_path.downcase == "#{project_namespace}.#{instance_pages_domain}"
      end

      def artifact_url(artifact, job)
        return unless artifact_url_available?(artifact, job)

        format(
          ARTIFACT_URL,
          host: namespace_url,
          project_path: project_path,
          job_id: job.id,
          artifact_path: artifact.path)
      end

      def artifact_url_available?(artifact, job)
        config.enabled &&
          config.artifacts_server &&
          ALLOWED_ARTIFACT_EXTENSIONS.include?(File.extname(artifact.name)) &&
          (config.access_control || job.project.public?)
      end

      # Defines the full hostname, eg. group.gitlab.io
      def hostname
        return instance_pages_domain if namespace_in_path?
        return project_path if is_namespace_homepage?
        return instance_pages_domain.prepend("#{subdomain}.") if subdomain.present?

        instance_pages_domain
      end

      def path_prefix
        return unless @options[:path_prefix].present?

        ::Gitlab::Utils.slugify(@options[:path_prefix], allow_dots: true).presence
      end

      private

      attr_reader :project, :project_path

      # Defines whether the Pages site is published on a unique subdomain
      # instead of a single subdomain for the entire namespace and a path
      # segment for each project.
      #
      # Examples:
      # unique_domain_enabled? is true: "https://my-project-12345.example.com
      # unique_domain_enabled? is false: "https://my-group.example.com/my-project
      def unique_domain_enabled?
        project.project_setting.pages_unique_domain_enabled?
      end

      # Defines whether the namespace should be included as part of the path
      # instead of using a subdomain. Useful for self-hosted instances where
      # auto-generated subdomains don't work.
      #
      # Examples:
      # namespace_in_path? is false: "https://my-group.example.com/my-project
      # namespace_in_path? is true: "https://example.com/my-group/my-project
      def namespace_in_path?
        config.namespace_in_path
      end

      def config
        Gitlab.config.pages
      end

      def config_url
        URI(config.url)
      end

      def protocol
        config.protocol
      end

      def subdomain
        return if namespace_in_path?
        return project.project_setting.pages_unique_domain if unique_domain_enabled?

        project_namespace
      end

      # Defines the top-most domain that is not autogenerated. For gitlab.com
      # this is `gitlab.io`. If you need the fully qualified domain for the
      # project, use `hostname` instead.
      def instance_pages_domain
        config_url.host
      end

      def port
        config.port
      end

      def path
        [
          namespace_path_segment,
          project_path_segment,
          path_prefix
        ].compact.join('/')
      end

      def namespace_path_segment
        return unless namespace_in_path?
        return project_namespace unless unique_domain_enabled?

        project.project_setting.pages_unique_domain
      end

      def project_path_segment
        return if is_namespace_homepage? || unique_domain_enabled?

        project_path
      end

      def default_url
        config_url.tap do |url|
          url.scheme = protocol
          url.host = hostname
          url.port = port
          url.path = "/#{path}" if path.present?
        end
      end

      def namespace_url
        config_url.tap do |url|
          url.port = port
          if namespace_in_path?
            url.path = "/#{project_namespace}"
          else
            url.host.prepend("#{project_namespace}.")
          end
        end
      end
    end
  end
end
