# frozen_string_literal: true

module API
  module Helpers
    module PackagesHelpers
      extend ::Gitlab::Utils::Override
      include ::Gitlab::Utils::StrongMemoize

      MAX_PACKAGE_FILE_SIZE = 50.megabytes.freeze

      def require_packages_enabled!
        not_found! unless ::Gitlab.config.packages.enabled
      end

      def require_dependency_proxy_enabled!
        not_found! unless ::Gitlab.config.dependency_proxy.enabled
      end

      def authorize_admin_package!(subject = user_project)
        authorize!(:admin_package, subject)
      end

      def authorize_read_package!(subject = user_project)
        authorize!(:read_package, subject.try(:packages_policy_subject) || subject)
      end

      def authorize_create_package!(subject = user_project)
        authorize!(:create_package, subject)
      end

      def authorize_destroy_package!(subject = user_project)
        authorize!(:destroy_package, subject)
      end

      def authorize_packages_access!(subject = user_project, required_permission = :read_package)
        require_packages_enabled!

        case required_permission
        when :read_package
          authorize_read_package!(subject)
        when :read_package_within_public_registries
          authorize!(required_permission, subject.packages_policy_subject)
        when :read_group
          authorize!(required_permission, subject)
        else
          forbidden!
        end
      end

      def authorize_workhorse!(
        subject: user_project,
        has_length: true,
        maximum_size: MAX_PACKAGE_FILE_SIZE,
        use_final_store_path: false
      )
        authorize_upload!(subject)

        status 200
        content_type Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE

        params = { has_length: has_length, use_final_store_path: use_final_store_path }
        params[:maximum_size] = maximum_size unless has_length
        params[:final_store_path_config] = { root_hash: subject.id } if use_final_store_path
        ::Packages::PackageFileUploader.workhorse_authorize(**params)
      end

      def authorize_upload!(subject = user_project)
        authorize_create_package!(subject)
        require_gitlab_workhorse!
      end

      override :user_project
      def user_project(action: :read_project)
        case action
        when :read_project
          super()
        when :read_package
          user_project_with_read_package
        else
          raise ArgumentError, "unexpected action: #{action}"
        end
      end

      # This function is similar to the `find_project!` function, but it considers the `read_package` ability.
      def user_project_with_read_package
        project = find_project(params[:id])

        return forbidden! unless authorized_project_scope?(project)

        project && authorize_job_token_policies!(project) && return

        return project if can?(current_user, :read_package, project&.packages_policy_subject)
        # guest users can have :read_project but not :read_package
        return forbidden! if can?(current_user, :read_project, project)
        return unauthorized! if authenticate_non_public?

        not_found!('Project')
      end
      strong_memoize_attr :user_project_with_read_package

      def track_package_event(action, scope, **args)
        service = ::Packages::CreateEventService.new(
          args[:project],
          current_user,
          namespace: args[:namespace],
          event_name: action,
          scope: scope
        )
        service.execute

        category = args.delete(:category) || self.options[:for].name
        args[:user] = current_user if current_user
        event_name = "i_package_#{scope}_user"
        ::Gitlab::Tracking.event(
          category,
          action.to_s,
          property: event_name,
          label: 'redis_hll_counters.user_packages.user_packages_total_unique_counts_monthly',
          context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis_hll, event: event_name).to_context],
          **args
        )

        if action.to_s == 'push_package' && service.originator_type == :deploy_token
          track_snowplow_event(
            'push_package_by_deploy_token',
            'package_pushed_using_deploy_token',
            category,
            args
          )
        elsif action.to_s == 'pull_package' && service.originator_type == :guest
          track_snowplow_event(
            'pull_package_by_guest',
            'package_pulled_by_guest',
            category,
            args
          )
        end
      end

      def present_package_file!(package_file, supports_direct_download: true, content_disposition: nil)
        package_file.package.touch_last_downloaded_at
        present_carrierwave_file!(
          package_file.file,
          supports_direct_download: supports_direct_download,
          content_disposition: content_disposition,
          extra_send_url_params: ::Packages::SsrfProtection.params_for(package_file.package)
        )
      end

      def protect_package!(package_name, package_type)
        service_response =
          ::Packages::Protection::CheckRuleExistenceService.for_push(
            project: user_project,
            current_user: current_user,
            params: { package_name: package_name, package_type: package_type }
          ).execute

        bad_request!(service_response.message) if service_response.error?
        forbidden!('Package protected.') if service_response[:protection_rule_exists?]
      end

      private

      def track_snowplow_event(action_name, snowplow_event_name, category, args)
        event_name = "i_package_#{action_name}"
        key_path = "counts.package_events_i_package_#{action_name}"
        context = Gitlab::Tracking::ServicePingContext.new(data_source: :redis, event: snowplow_event_name).to_context

        Gitlab::Tracking.event(
          category,
          action_name,
          property: event_name,
          label: key_path,
          context: [context],
          **args
        )
      end
    end
  end
end
