# frozen_string_literal: true

module ReleaseTools
  module ReleaseManagers
    # Obtaining of release managers for a given major/minor release.
    class Schedule
      include ::SemanticLogger::Loggable

      # Raised when release managers for the specified version can't be found
      VersionNotFoundError = Class.new(StandardError)

      # The base interval for retrying operations that failed, in seconds.
      #
      # This is set higher than the default as obtaining the release managers
      # schedule can time out, and it often takes a while before these requests
      # stop timing out.
      RETRY_INTERVAL = 5

      # Releases SSOT - Contains releases and release manager information.
      RELEASES_YAML = 'https://gitlab.com/gitlab-com/www-gitlab-com/-/raw/master/data/releases.yml'

      APPSEC_GROUP_ID = 4_654_006

      def initialize
        @schedule_yaml = nil
      end

      # Returns the scheduled major.minor version for today.
      #
      # To be deprecated https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/19851
      #
      # @return [ReleaseTools::Version|NilClass]
      def active_version
        Version.new(ReleaseTools::GitlabReleasesGemClient.active_version)
      end

      # Returns an Array of users for the current milestone release managers
      #
      # @return [Array<Gitlab::ObjectifiedHash>]
      def active_release_managers
        authorized_release_managers(active_version)
      end

      # Returns an Array of users for the current milestone release managers
      #
      # @return [Array<Gitlab::ObjectifiedHash>]
      def active_appsec_release_managers
        # Appsec release managers are listed for the previous release
        appsec_release_managers(Version.new(active_version.previous_minor))
      end

      # Returns the scheduled major.minor version for the given date.
      #
      # To be deprecated https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/19851
      #
      # @param [Date|Time] date
      # @return [ReleaseTools::Version|NilClass]
      def version_for_date(date)
        Version.new(ReleaseTools::GitlabReleasesGemClient.version_for_date(date))
      rescue StandardError
        nil
      end

      # Returns the user IDs of the release managers for the current version.
      # Used to assign to resources such as Issues.
      #
      # @param [ReleaseTools::Version] version
      # @return [Array<Integer>]
      def ids_for_version(version)
        authorized_release_managers(version).collect(&:id)
      end

      # Returns the usernames of the release managers for the current version.
      # Used to add/remove access to groups at various GitLab instances.
      #
      # @param [ReleaseTools::Version] version
      # @return [Array<String>]
      def usernames_for_version(version)
        authorized_release_managers(version).collect(&:username)
      end

      # Returns an Array of users authorized for release manager tasks for current version.
      #
      # @param [ReleaseTools::Version] version
      # @return [Array<Gitlab::ObjectifiedHash>]
      def authorized_release_managers(version)
        names = release_manager_names_from_yaml(version)
        client = ReleaseManagers::Client.new

        Definitions.new.find_by_name(names).map do |release_manager|
          client.get_user(release_manager.production)
        end
      end

      # Returns a Hash mapping release manager names with all their attributes.
      #
      # @return [Hash<String, Gitlab::ObjectifiedHash>]
      def group_members
        members =
          begin
            ReleaseManagers::Client.new.members
          rescue StandardError
            []
          end

        members.each_with_object({}) do |user, hash|
          hash[user.name] = user
        end
      end

      # @return [Array<Hash>]
      def schedule_yaml
        @schedule_yaml ||=
          begin
            YAML.safe_load(download_schedule) || []
          rescue StandardError
            []
          end
      end

      private

      def appsec_release_managers(version)
        names = release_manager_names_from_yaml(version, appsec: true)
        client = ReleaseTools::GitlabClient.client

        appsec_members = Retriable.with_context(:api) do
          client.group_members(APPSEC_GROUP_ID)
        end

        appsec_members.select! { |member| names.include?(member.name) }
      end

      def release_manager_names_from_yaml(version, appsec: false)
        not_found = -> { raise VersionNotFoundError }
        minor = version.to_minor

        names = schedule_yaml
          .find(not_found) { |row| row['version'] == minor }

        return names['appsec'] if appsec

        names['manager_americas'] | names['manager_apac_emea']
      end

      def download_schedule
        Retriable.retriable(base_interval: RETRY_INTERVAL) do
          HTTP.get(RELEASES_YAML).to_s
        end
      end
    end
  end
end
