# frozen_string_literal: true

describe Gitlab::QA::Runtime::Env do
  around do |example|
    # Reset any already defined env variables (e.g. on CI)
    ClimateControl.modify described_class::ENV_VARIABLES.keys.zip([nil]).to_h do
      example.run
    end
  end

  shared_examples 'boolean method' do |**kwargs|
    it_behaves_like 'boolean method with parameter', kwargs
  end

  shared_examples 'boolean method with parameter' do |method:, env_key:, default:, param: nil|
    context 'when there is an env variable set' do
      it 'returns false when falsey values specified' do
        ClimateControl.modify(env_key => 'false') do
          expect(described_class.public_send(method, *param)).to be_falsey
        end

        ClimateControl.modify(env_key => 'no') do
          expect(described_class.public_send(method, *param)).to be_falsey
        end

        ClimateControl.modify(env_key => '0') do
          expect(described_class.public_send(method, *param)).to be_falsey
        end
      end

      it 'returns true when anything else specified' do
        ClimateControl.modify(env_key => 'true') do
          expect(described_class.public_send(method, *param)).to be_truthy
        end

        ClimateControl.modify(env_key => '1') do
          expect(described_class.public_send(method, *param)).to be_truthy
        end

        ClimateControl.modify(env_key => 'anything') do
          expect(described_class.public_send(method, *param)).to be_truthy
        end
      end
    end

    context 'when there is no env variable set' do
      it "returns the default, #{default}" do
        ClimateControl.modify(env_key => nil) do
          expect(described_class.public_send(method, *param)).to be(default)
        end
      end
    end
  end

  describe '.skip_pull?' do
    it_behaves_like 'boolean method',
      method: :skip_pull?,
      env_key: 'QA_SKIP_PULL',
      default: false
  end

  describe '.geo_failover?' do
    it_behaves_like 'boolean method',
      method: :geo_failover?,
      env_key: 'GEO_FAILOVER',
      default: false
  end

  describe '.run_id' do
    around do |example|
      described_class.instance_variable_set(:@run_id, nil)
      example.run
      described_class.instance_variable_set(:@run_id, nil)
    end

    it 'returns a unique run id' do
      now = Time.now
      allow(Time).to receive(:now).and_return(now)
      allow(SecureRandom).to receive(:hex).and_return('abc123')

      expect(described_class.run_id).to eq "gitlab-qa-run-#{now.strftime('%Y-%m-%d-%H-%M-%S')}-abc123"
      expect(described_class.run_id).to eq "gitlab-qa-run-#{now.strftime('%Y-%m-%d-%H-%M-%S')}-abc123"
    end
  end

  describe '.dev_access_token_variable' do
    context 'when there is an env variable set' do
      around do |example|
        ClimateControl.modify(GITLAB_QA_DEV_ACCESS_TOKEN: 'abc123') { example.run }
      end

      it 'returns directory defined in environment variable' do
        expect(described_class.dev_access_token_variable).to eq '$GITLAB_QA_DEV_ACCESS_TOKEN'
      end
    end

    context 'when there is no env variable set' do
      around do |example|
        ClimateControl.modify(GITLAB_QA_DEV_ACCESS_TOKEN: nil) { example.run }
      end

      it 'returns a default screenshots directory' do
        expect(described_class.dev_access_token_variable).to be_nil
      end
    end
  end

  describe '.host_artifacts_dir' do
    around do |example|
      described_class.instance_variable_set(:@host_artifacts_dir, nil)
      example.run
      described_class.instance_variable_set(:@host_artifacts_dir, nil)
    end

    context 'when there is an env variable set' do
      around do |example|
        ClimateControl.modify(QA_ARTIFACTS_DIR: '/tmp') { example.run }
      end

      it 'returns directory defined in environment variable' do
        expect(described_class.host_artifacts_dir).to eq "/tmp/#{described_class.run_id}"
      end
    end

    context 'when there is no env variable set' do
      around do |example|
        ClimateControl.modify(QA_ARTIFACTS_DIR: nil) { example.run }
      end

      it 'returns a default screenshots directory' do
        expect(described_class.host_artifacts_dir)
          .to eq "/tmp/gitlab-qa/#{described_class.run_id}"
      end
    end
  end

  describe '.variables' do
    around do |example|
      ClimateControl.modify(
        GITLAB_USERNAME: 'root',
        GITLAB_QA_ACCESS_TOKEN: nil,
        EE_LICENSE: nil) { example.run }
    end

    before do
      described_class.user_username = nil
      described_class.user_password = nil
      described_class.user_type = nil
      described_class.gitlab_url = nil
      described_class.ee_license = nil
    end

    context 'with undefined QA_ variables' do
      around do |example|
        ClimateControl.modify(QA_SOME_TEST_VAR: 'var') { example.run }
      end

      it 'returns QA_ prefixed variables that are not explicitly defined' do
        expect(described_class.variables).to include('QA_SOME_TEST_VAR' => '$QA_SOME_TEST_VAR')
      end
    end

    it 'returns only these delegated variables that are set' do
      expect(described_class.variables).to include({ 'GITLAB_USERNAME' => '$GITLAB_USERNAME' })
    end

    it 'prefers environment variables to defined values' do
      described_class.user_username = 'tanuki'

      expect(described_class.variables).to include({ 'GITLAB_USERNAME' => '$GITLAB_USERNAME' })
    end

    it 'returns values that have been overriden' do
      described_class.user_password = 'tanuki'
      described_class.user_type = 'ldap'
      described_class.gitlab_url = 'http://localhost:9999'

      expect(described_class.variables).to include({ 'GITLAB_USERNAME' => '$GITLAB_USERNAME',
                                                     'GITLAB_PASSWORD' => 'tanuki',
                                                     'GITLAB_USER_TYPE' => 'ldap',
                                                     'GITLAB_URL' => 'http://localhost:9999' })
    end
  end

  describe '.require_gcs_with_cdn_environment!' do
    around do |example|
      ClimateControl.modify(
        GOOGLE_CDN_JSON_KEY: nil,
        GCS_CDN_BUCKET_NAME: nil,
        GOOGLE_CDN_LB: nil,
        GOOGLE_CDN_SIGNURL_KEY: nil,
        GOOGLE_CDN_SIGNURL_KEY_NAME: nil
      ) { example.run }
    end

    it 'raises an error when required variables are not present' do
      expect { described_class.require_gcs_with_cdn_environment! }.to raise_error(ArgumentError)
    end

    it 'raises an error with detailed message when one required element is not present' do
      ClimateControl.modify(
        # GOOGLE_CDN_JSON_KEY: nil,
        GCS_CDN_BUCKET_NAME: 'testbucket',
        GOOGLE_CDN_LB: 'http://123.4.5.67',
        GOOGLE_CDN_SIGNURL_KEY: 'asdf1234',
        GOOGLE_CDN_SIGNURL_KEY_NAME: 'arandomkey'
      ) do
        expect { described_class.require_gcs_with_cdn_environment! }.to raise_error(/GOOGLE_CDN_JSON_KEY/)
      end
    end

    it 'doesnt raise an error when all variables are present' do
      ClimateControl.modify(
        GOOGLE_CDN_JSON_KEY: '{"jsonkey"}',
        GCS_CDN_BUCKET_NAME: 'testbucket',
        GOOGLE_CDN_LB: 'http://123.4.5.67',
        GOOGLE_CDN_SIGNURL_KEY: 'asdf1234',
        GOOGLE_CDN_SIGNURL_KEY_NAME: 'arandomkey'
      ) do
        expect { described_class.require_gcs_with_cdn_environment! }.not_to raise_error
      end
    end
  end

  describe '.log_level' do
    let(:log_level) { nil }

    around do |example|
      ClimateControl.modify(QA_LOG_LEVEL: log_level) { example.run }
    end

    context 'without explicitly set log level' do
      it 'return INFO log level' do
        expect(described_class.log_level).to eq('INFO')
      end
    end

    context 'with explicitly set log level' do
      let(:log_level) { 'debug' }

      it 'returns correct upcased log level' do
        expect(described_class.log_level).to eq(log_level.upcase)
      end
    end
  end

  describe '.log_path' do
    let(:log_path) { nil }

    around do |example|
      ClimateControl.modify(QA_LOG_PATH: log_path) { example.run }
    end

    context 'without explicitly set log path' do
      it 'returns default log path' do
        expect(described_class.log_path).to eq(described_class.host_artifacts_dir)
      end
    end

    context 'with explicitly set log level' do
      let(:log_path) { '/tmp/custom/log' }

      it 'returns configured log path' do
        expect(described_class.log_path).to eq(log_path)
      end
    end
  end

  describe '.docker_add_hosts' do
    context 'when there is an env variable set' do
      around do |example|
        ClimateControl.modify(QA_DOCKER_ADD_HOSTS: 'docker:10.0.0.1,google.com:1.1.1.1') { example.run }
      end

      it 'returns hosts defined in environment variable' do
        expect(described_class.docker_add_hosts).to eq %w[docker:10.0.0.1 google.com:1.1.1.1]
      end
    end

    context 'when there is no env variable set' do
      around do |example|
        ClimateControl.modify(QA_DOCKER_ADD_HOSTS: nil) { example.run }
      end

      it 'returns a empty array' do
        expect(described_class.docker_add_hosts).to eq []
      end
    end
  end

  describe '.variables_to_mask' do
    context 'when there is an env variable set' do
      let(:var_name) { 'WORKSPACES_DOMAIN_CERT' }
      let(:file_var) { '/path/to/file_var' }
      let(:secret) { 'secret' }

      it 'returns the variable and value to mask' do
        ClimateControl.modify(var_name => file_var) do
          stub_const(
            'Gitlab::QA::Runtime::Env::ENV_VARIABLES',
            var_name => { name: :workspaces_domain_cert, type: :file }
          )

          expect(File).to receive(:exist?).and_return(true)
          expect(File).to receive(:read).and_return(secret)
          expect(described_class.variables_to_mask).to include(secret)
        end
      end
    end

    context 'when there is no env variable set' do
      it 'returns a empty array' do
        expect(described_class.variables_to_mask).to eq []
      end
    end
  end

  describe '.docker_network' do
    context 'when there is an env variable set' do
      around do |example|
        ClimateControl.modify(QA_DOCKER_NETWORK: 'custom') { example.run }
      end

      it 'returns network defined in environment variable' do
        expect(described_class.docker_network).to eq 'custom'
      end
    end

    context 'when there is no env variable set' do
      it 'returns a default network name' do
        expect(described_class.docker_network).to eq 'test'
      end
    end
  end
end
