# frozen_string_literal: true

module ReleaseTools
  module PublicRelease
    # A release of GitLab Operator using the API
    class GitlabOperatorRelease
      include Release
      include Services::SyncRefsHelper

      attr_reader :version, :client, :chart_version, :gitlab_version, :helm_finder

      # File containing versions of GitLab Helm chart supported by GitLab
      # Operator
      CHART_VERSIONS_FILE = 'CHART_VERSIONS'
      DEFAULT_BRANCH = Project::GitlabOperator.default_branch
      CHART_YML_FILE = 'deploy/chart/Chart.yaml'
      WAIT_SYNC_INTERVALS = Array.new(10, 60)

      TagNotFoundInBuildMirror = Class.new(StandardError) do
        def message
          'The tag was not found in Build mirror'
        end
      end

      def initialize(
        version,
        chart_version,
        gitlab_version,
        client: GitlabClient,
        commit: nil
      )
        @version = version
        @chart_version = chart_version
        @gitlab_version = gitlab_version.to_ee
        @client = client
        @commit = commit

        @helm_finder = ::ReleaseTools::Helm::HelmVersionFinder.new
      end

      def execute
        if gitlab_version.rc?
          logger.info(
            'Not releasing GitLab Operator for an RC',
            version: version,
            chart_version: chart_version,
            gitlab_version: gitlab_version
          )

          return
        end

        if backport_chart_release?
          logger.info(
            'Not releasing GitLab Operator for backport Chart release',
            version: version,
            chart_version: chart_version,
            gitlab_version: gitlab_version
          )

          return
        end

        logger.info(
          'Starting release of GitLab Operator',
          version: version,
          chart_version: chart_version,
          gitlab_version: gitlab_version
        )

        return if SharedStatus.dry_run?

        create_target_branch
        compile_changelog

        supported_versions = helm_finder.latest_minor_versions
        update_chart_yml_file
        update_chart_files(supported_versions)
        create_tag(supported_versions)

        # We're syncing Operator refs as part of release because Operator is
        # tagged while other components are being published, and the general
        # sync has already happened. Because the sync involves fetching refs
        # from dev and pushing them to other mirrors, we first wait for the
        # tag to reach dev.
        wait_for_tag_in_dev
        sync_branch_and_tag

        notify_slack(project, version)
      end

      def wait_for_tag_in_dev
        return if Feature.enabled?(:skip_operator_wait_for_tag_in_dev)

        logger.info('Waiting for Operator tag to be present in Build mirror')

        remotes = project::REMOTES
        repository = RemoteRepository.get(remotes, global_depth: 50, branch: target_branch)

        Retriable.retriable(intervals: WAIT_SYNC_INTERVALS, on: TagNotFoundInBuildMirror) do
          status = repository.fetch("refs/tags/#{version}", remote: :dev)

          raise TagNotFoundInBuildMirror unless status

          logger.info('Operator tag found in Build mirror')
        end
      end

      def sync_branch_and_tag
        sync_branches(Project::GitlabOperator, target_branch)
        sync_tags(Project::GitlabOperator, version.to_s)
      end

      def update_chart_files(supported_versions)
        # Commit to target branch
        commit_version_files(
          target_branch,
          { CHART_VERSIONS_FILE => supported_versions.join("\n") },
          message: "Update CHART_VERSIONS for GitLab Chart release #{chart_version}"
        )

        # Commit to default branch (master/main)
        commit_version_files(
          DEFAULT_BRANCH,
          { CHART_VERSIONS_FILE => supported_versions.join("\n") },
          message: "Update CHART_VERSIONS for GitLab Chart release #{chart_version}"
        )
      end

      def update_chart_yml_file
        content = read_file(CHART_YML_FILE)
        chart_details = YAML.safe_load(content)

        chart_details['version'] = version.to_s
        chart_details['appVersion'] = version.to_s

        commit_version_files(
          target_branch,
          { CHART_YML_FILE => YAML.dump(chart_details) },
          message: "Update Chart version to #{version}\n\n [ci skip]"
        )

        # Commit to default branch (master/main)
        commit_version_files(
          DEFAULT_BRANCH,
          { CHART_YML_FILE => YAML.dump(chart_details) },
          message: "Update Chart version to #{version}\n\n [ci skip]"
        )
      end

      def compile_changelog
        logger.info('Compiling changelog', project: project_path)

        ChangelogCompiler
          .new(project_path, client: client)
          .compile(version, branch: target_branch)
      end

      def create_tag(supported_versions)
        logger.info('Creating tag', tag: tag_name, project: project_path)

        client.find_or_create_tag(
          project_path,
          version.to_s,
          target_branch,
          message: "Version #{version} - supports GitLab Charts #{supported_versions.join(', ')}"
        )
      end

      def read_file(file, project: project_path, branch: target_branch)
        Retriable.with_context(:api) do
          client.file_contents(project, file, branch).strip
        end
      end

      def backport_chart_release?
        latest_chart_version = helm_finder.latest_version

        chart_version != latest_chart_version && helm_finder.sorted_versions([chart_version, latest_chart_version]).last == latest_chart_version
      end

      def project
        Project::GitlabOperator
      end

      def source_for_target_branch
        @commit || DEFAULT_BRANCH
      end
    end
  end
end
