# frozen_string_literal: true

require 'spec_helper'

describe ReleaseTools::PipelineTracer::MetricsService do
  describe '.from_pipeline_url' do
    it 'creates Service instance' do
      url = 'https://ops.gitlab.net/gitlab-org/release/tools/-/pipelines/1590941'

      instance = described_class.from_pipeline_url(
        url,
        version: 'pipeline 1',
        depth: 2,
        upstream_pipeline_name: 'Test pipeline'
      )

      pipeline = instance.instance_variable_get(:@pipeline)
      version = instance.instance_variable_get(:@version)
      depth = instance.instance_variable_get(:@depth)
      upstream_pipeline_name = instance.instance_variable_get(:@upstream_pipeline_name)

      expect(pipeline.gitlab_instance).to eq('ops.gitlab.net')
      expect(pipeline.id).to eq('1590941')
      expect(pipeline.project).to eq('gitlab-org/release/tools')
      expect(version).to eq('pipeline 1')
      expect(depth).to eq(2)
      expect(upstream_pipeline_name).to eq('Test pipeline')
    end
  end

  describe '#execute' do
    subject(:instance) do
      described_class.new(
        pipeline: pipeline,
        version: version,
        depth: depth,
        upstream_pipeline_name: upstream_pipeline_name
      )
    end

    let(:gitlab_instance) { 'ops.gitlab.net' }
    let(:project) { 'gitlab-org/release/tools' }
    let(:pipeline_id) { '1590941' }

    let(:gitlab_client) do
      case gitlab_instance
      when 'ops.gitlab.net'
        ReleaseTools::GitlabOpsClient
      when 'dev.gitlab.org'
        ReleaseTools::GitlabDevClient
      else
        ReleaseTools::GitlabClient
      end
    end

    let(:pipeline) do
      ReleaseTools::PipelineTracer::Pipeline.new(gitlab_instance.to_s, project.to_s, pipeline_id.to_s)
    end

    let(:upstream_pipeline_name) { 'Upstream pipeline' }
    let(:depth) { 2 }
    let(:version) { '15.10.202303221120-355b04fb8d1.936857331ed' }
    let(:metrics_client) { instance_double(ReleaseTools::Metrics::Client) }
    let(:current_pipeline_name) { 'Deployment pipeline - 15.10.202303221120' }

    let(:job1) { ReleaseTools::PipelineTracer::Job.new(jobs[0], gitlab_client) }

    let(:pipeline_duration_labels) do
      "#{project},#{version},#{pipeline_details.status},#{current_pipeline_name},#{pipeline.id},#{pipeline_details.web_url},gstg,cny,#{upstream_pipeline_name}"
    end

    let(:pipeline_duration_histogram_labels) do
      pipeline_details.status.to_s
    end

    let(:pipeline_details) do
      build(
        :pipeline,
        :success,
        id: pipeline.id,
        name: current_pipeline_name,
        web_url: "https://#{gitlab_instance}/#{project}/-/pipelines/#{pipeline.id}"
      )
    end

    let(:job_duration_labels) do
      "job1,stage1,success,#{project},#{version},,,job1,https://example.com/foo/bar/-/jobs/#{job.id},#{job.id},#{pipeline.id},#{pipeline.details.name}"
    end

    let(:jobs) do
      [
        build(
          :job, :success, name: 'job1'
        ),
        build(
          :job, :running, name: 'job2'
        )
      ]
    end

    let(:bridge_jobs) { [build(:bridge_job, :success, name: 'bridge_job1')] }

    let(:job) do
      ReleaseTools::PipelineTracer::Job.new(jobs[0], gitlab_client)
    end

    before do
      allow(gitlab_client)
        .to receive_messages(pipeline: pipeline_details, pipeline_variables: [build(:gitlab_response, key: 'DEPLOY_ENVIRONMENT', value: 'gstg-cny')])

      allow(ReleaseTools::PipelineTracer::Job).to receive(:new).and_call_original
      allow(ReleaseTools::PipelineTracer::Job).to receive(:new).with(jobs[0], gitlab_client).and_return(job)
      allow(job).to receive(:real_time_duration).and_return(300)

      allow(ReleaseTools::Metrics::Client).to receive(:new).and_return(metrics_client)

      allow(pipeline)
        .to receive_messages(real_time_duration: 200, jobs: Gitlab::PaginatedResponse.new(jobs), bridge_jobs: Gitlab::PaginatedResponse.new(bridge_jobs))

      allow(described_class)
        .to receive(:from_pipeline_url)
        .with(
          bridge_jobs.first.downstream_pipeline.web_url,
          {
            version: version,
            depth: depth - 1,
            upstream_pipeline_name: pipeline.details.name
          }
        )
        .and_return(double(execute: nil))

      allow(metrics_client).to receive(:set)
      allow(metrics_client).to receive(:observe)

      allow(ReleaseTools::PipelineTracer::Job)
        .to receive(:new)
        .with(jobs[0], gitlab_client)
        .and_return(job1)

      allow(job1).to receive(:triggered_downstream_pipeline?).and_return(false)
    end

    it 'creates metrics for pipeline' do
      expect(metrics_client)
        .to receive(:set)
        .with('deployment_pipeline_duration_seconds', pipeline.real_time_duration, { labels: pipeline_duration_labels })

      expect(metrics_client)
        .to receive(:observe)
        .with('deployment_coordinator_pipeline_duration_seconds', pipeline.real_time_duration, { labels: pipeline_duration_histogram_labels })

      without_dry_run { instance.execute }
    end

    it 'creates metric for jobs' do
      expect(metrics_client)
        .to receive(:set)
        .with('deployment_job_duration_seconds', job.real_time_duration, { labels: job_duration_labels })

      expect(metrics_client)
        .not_to receive(:set)
        .with('deployment_job_duration_seconds', anything, { labels: include("job2") })

      without_dry_run { instance.execute }
    end

    it 'creates metric for bridge jobs' do
      expect(described_class)
        .to receive(:from_pipeline_url)
        .with(
          bridge_jobs[0].downstream_pipeline.web_url,
          {
            version: version,
            depth: 1,
            upstream_pipeline_name: pipeline.details.name
          }
        )
        .and_return(double(execute: nil))

      without_dry_run { instance.execute }
    end

    it 'does not call triggered_pipeline_url if triggered_downstream_pipeline? is false' do
      expect(job1).to receive(:triggered_downstream_pipeline?)
      expect(job1).not_to receive(:triggered_pipeline_url)
      expect(described_class)
        .not_to receive(:from_pipeline_url)
        .with('https://url.local', { version: version, depth: depth - 1, upstream_pipeline_name: pipeline.details.name })

      without_dry_run { instance.execute }
    end

    context 'with no end_time' do
      before do
        allow(pipeline).to receive(:end_time).and_return(nil)
      end

      it 'does not generate duration metrics' do
        expect(metrics_client).not_to receive(:set)
        expect(metrics_client).not_to receive(:observe)

        without_dry_run { instance.execute }
      end
    end

    context 'with environment in name' do
      let(:job_duration_labels) do
        "#{job.name},stage1,success,#{project},#{version},gstg,cny,metrics:deployment_completed:,https://example.com/foo/bar/-/jobs/#{job.id},#{job.id},#{pipeline.id},#{pipeline.details.name}"
      end

      let(:jobs) do
        [build(:job, :success, name: 'metrics:deployment_completed:gstg-cny'), build(:job, :running, name: 'job2')]
      end

      it 'creates metric for jobs' do
        expect(metrics_client)
          .to receive(:set)
          .with('deployment_job_duration_seconds', job.real_time_duration, { labels: job_duration_labels })

        without_dry_run { instance.execute }
      end
    end

    context 'when depth is invalid' do
      let(:depth) { 4 }

      it 'raises error' do
        expect { instance.execute }.to raise_error(described_class::InvalidDepthError, 'Depth must be between 0 and 3')
      end
    end

    context 'when depth is 0' do
      subject(:instance) do
        described_class.new(pipeline: pipeline, version: version, depth: 0, upstream_pipeline_name: upstream_pipeline_name)
      end

      it 'does not create job duration metric' do
        expect(metrics_client)
          .to receive(:set)
          .with('deployment_pipeline_duration_seconds', pipeline.real_time_duration, { labels: pipeline_duration_labels })

        expect(metrics_client).not_to receive(:set).with('deployment_job_duration_seconds', any_args)

        expect(described_class).not_to receive(:from_pipeline_url)

        without_dry_run { instance.execute }
      end
    end

    context 'triggered job' do
      before do
        allow(job1).to receive_messages(triggered_downstream_pipeline?: true, triggered_pipeline_url: 'https://url.local')
      end

      it 'executes MetricsService' do
        expect(job1).to receive(:triggered_downstream_pipeline?)
        expect(job1).to receive(:triggered_pipeline_url)

        expect(described_class)
          .to receive(:from_pipeline_url)
          .with(
            'https://url.local',
            {
              version: version,
              depth: depth - 1,
              upstream_pipeline_name: pipeline.details.name
            }
          )
          .and_return(instance_spy(described_class, execute: nil))

        without_dry_run { instance.execute }
      end

      context 'with nil pipeline_name' do
        let(:current_pipeline_name) { nil }

        it 'calls from_pipeline_url with upstream_pipeline_name set to nil' do
          expect(job1).to receive(:triggered_downstream_pipeline?)
          expect(job1).to receive(:triggered_pipeline_url)

          expect(described_class)
          .to receive(:from_pipeline_url)
          .with(
            'https://url.local',
            {
              version: version,
              depth: depth - 1,
              upstream_pipeline_name: nil
            }
          )
          .and_return(instance_spy(described_class, execute: nil))

          without_dry_run { instance.execute }
        end
      end
    end

    context 'with nil upstream_pipeline_name' do
      let(:upstream_pipeline_name) { nil }

      it 'sets upstream_pipeline_name label to blank string' do
        expect(metrics_client)
          .to receive(:set)
          .with('deployment_pipeline_duration_seconds', pipeline.real_time_duration, { labels: pipeline_duration_labels })

        expect(metrics_client)
          .not_to receive(:observe)
          .with('deployer_pipeline_duration_histogram_seconds', pipeline.real_time_duration, { labels: pipeline_duration_histogram_labels })

        without_dry_run { instance.execute }
      end
    end

    context 'with dry run' do
      it 'does nothing' do
        expect(metrics_client).not_to receive(:set)
        expect(metrics_client).not_to receive(:observe)

        instance.execute
      end
    end

    context 'when quality pipeline is traced' do
      let(:project) { 'gitlab-org/quality/canary' }
      let(:current_pipeline_name) { 'Deployment QA pipeline - 17.11.202503252206-0b36d04e4a9.6ba20f9b71c' }

      it 'does not submit histogram metric' do
        expect(metrics_client)
          .to receive(:set)
          .with('deployment_pipeline_duration_seconds', pipeline.real_time_duration, { labels: pipeline_duration_labels })

        expect(metrics_client)
          .not_to receive(:observe)

        without_dry_run { instance.execute }
      end
    end

    context 'when Omnibus pipeline is traced' do
      let(:gitlab_instance) { 'dev.gitlab.org' }
      let(:project) { 'gitlab/omnibus-gitlab' }
      let(:upstream_pipeline_name) { 'Deployment pipeline - 17.11.202503252206' }
      let(:current_pipeline_name) { 'AUTO_DEPLOY_BUILD_PIPELINE' }
      let(:pipeline_status) { 'success' }

      it 'submits histogram metrics' do
        expect(metrics_client)
          .to receive(:set)
          .with('deployment_pipeline_duration_seconds', pipeline.real_time_duration, { labels: pipeline_duration_labels })

        expect(metrics_client)
          .not_to receive(:observe)
          .with('deployment_coordinator_pipeline_duration_seconds', anything, anything)

        expect(metrics_client)
          .to receive(:observe)
          .with('deployment_packager_omnibus_pipeline_duration_seconds', pipeline.real_time_duration, { labels: pipeline_duration_histogram_labels })

        without_dry_run { instance.execute }
      end
    end

    context 'when CNG pipeline is traced' do
      let(:gitlab_instance) { 'dev.gitlab.org' }
      let(:upstream_pipeline_name) { 'Deployment pipeline - 17.11.202503252206' }
      let(:current_pipeline_name) { 'AUTO_DEPLOY_BUILD_PIPELINE' }
      let(:project) { 'gitlab/charts/components/images' }
      let(:pipeline_status) { 'running' }

      it 'submits histogram metrics' do
        expect(metrics_client)
          .to receive(:set)
          .with('deployment_pipeline_duration_seconds', pipeline.real_time_duration, { labels: pipeline_duration_labels })

        expect(metrics_client)
          .not_to receive(:observe)
          .with('deployment_coordinator_pipeline_duration_seconds', anything, anything)

        expect(metrics_client)
          .to receive(:observe)
          .with('deployment_packager_cng_pipeline_duration_seconds', pipeline.real_time_duration, { labels: pipeline_duration_histogram_labels })

        without_dry_run { instance.execute }
      end
    end
  end
end
