# frozen_string_literal: true

module ReleaseTools
  module PipelineTracer
    # Helper class for the PipelineTracer module
    class Pipeline
      include ::SemanticLogger::Loggable

      PIPELINE_URL_REGEX = %r{https://(?<instance>dev\.gitlab\.org|ops\.gitlab\.net|gitlab\.com)/(?<project>.+)/-/pipelines/(?<pipeline_id>\d+)}

      FINISHED_STATUSES = %w[success failed canceled].freeze

      # Jobs that should be ignored when calculating end_time of the pipeline.
      # This is used to ignore the metrics:trace-pipeline job since the job runs much later than
      # the rest of the pipeline and should not count towards the duration of the pipeline.
      IGNORED_JOBS_FOR_END_TIME_CALCULATION = %w[metrics:trace-pipeline].freeze

      attr_reader :gitlab_instance, :project, :id

      # @param [string] pipeline_url
      def self.from_url(pipeline_url)
        match = PIPELINE_URL_REGEX.match(pipeline_url)

        new(match[:instance], match[:project], match[:pipeline_id])
      end

      # @param [string] gitlab_instance Ex: 'ops.gitlab.net', 'dev.gitlab.org', 'gitlab.com'
      # @param [string] project Ex: 'gitlab-org/release/tools'
      # @param [string, integer] pipeline_id Ex: '12345' or 12345
      def initialize(gitlab_instance, project, pipeline_id)
        @gitlab_instance = gitlab_instance
        @project = project
        @id = pipeline_id
      end

      def details
        @details ||=
          Retriable.with_context(:api) do
            client.pipeline(project, id)
          end
      end

      def bridge_jobs
        @bridge_jobs ||=
          Retriable.with_context(:api) do
            client.pipeline_bridges(project, id)
          end
      end

      def jobs
        @jobs ||=
          Retriable.with_context(:api) do
            client.pipeline_jobs(project, id, { include_retried: true, per_page: 100 })
          end
      end

      def url
        "https://#{gitlab_instance}/#{project}/-/pipelines/#{id}"
      end

      def start_time
        return @start_time if defined?(@start_time)

        logger.info(
          'Start time of pipeline',
          pipeline_url: url,
          started_at: details.started_at,
          created_at: details.created_at
        )

        @start_time ||= details.started_at || details.created_at
      end

      def end_time
        @end_time ||=
          if valid_finished_at?

            logger.info(
              'Has valid finished_at',
              pipeline_url: url,
              finished_at: details.finished_at,
              updated_at: details.updated_at
            )

            details.finished_at || details.updated_at
          else
            calculate_end_time
          end
      end

      def real_time_duration
        Time.parse(end_time) - Time.parse(start_time)
      end

      def root_attributes
        {
          user: details.user.username,
          web_url: details.web_url,
          created_at: details.created_at,
          updated_at: details.updated_at,
          ref: details.ref,
          duration: details.duration || 0,
          queued_duration: details.queued_duration || 0,
          status: details.status,
          started_at: details.started_at,
          finished_at: details.finished_at || 0
        }
      end

      def valid_finished_at?
        FINISHED_STATUSES.include?(details.status)
      end

      def deploy_environment
        deploy_env = pipeline_variables.detect { |v| v.key == 'DEPLOY_ENVIRONMENT' }
        return unless deploy_env

        deploy_env.value
      end

      def client
        @client ||=
          case gitlab_instance
          when 'dev.gitlab.org'
            GitlabDevClient
          when 'ops.gitlab.net'
            GitlabOpsClient
          when 'gitlab.com'
            GitlabClient
          else
            raise "Unknown Gitlab instance: #{gitlab_instance}"
          end
      end

      private

      # The end_time of the pipeline should be the finished_at value of the last job to complete.
      def calculate_end_time
        last_job_end_time = last_job_finished_at
        last_bridge_job_end_time = last_bridge_job_finished_at

        logger.info(
          'Calculating end time',
          pipeline_url: url,
          last_job_finished_at: last_job_end_time&.iso8601,
          last_bridge_job_finished_at: last_bridge_job_end_time&.iso8601
        )

        [last_job_end_time, last_bridge_job_end_time].compact.max&.iso8601
      end

      def last_job_finished_at
        all_finished_at = jobs.auto_paginate.collect do |job|
          next if IGNORED_JOBS_FOR_END_TIME_CALCULATION.include?(job.name)
          next unless job.finished_at

          Time.parse(job.finished_at)
        end

        all_finished_at.compact.max
      end

      def last_bridge_job_finished_at
        bridge_jobs
          .auto_paginate
          .filter_map { |job| Time.parse(job.finished_at) if job.finished_at }
          .max
      end

      def pipeline_variables
        client.pipeline_variables(project, id)
      end
    end
  end
end
