# frozen_string_literal: true

RSpec.describe FaradayMiddleware::OAuth2 do
  def query_params(env)
    Faraday::Utils.parse_query env[:url].query
  end

  def auth_header(env)
    env[:request_headers]['Authorization']
  end

  def perform(params = {}, headers = {})
    env = {
      url: URI("http://example.com/?#{Faraday::Utils.build_query(params)}"),
      request_headers: Faraday::Utils::Headers.new.update(headers)
    }
    app = make_app
    app.call(faraday_env(env))
  end

  def make_app
    described_class.new(->(env) { env }, *Array(options))
  end

  context 'no token configured' do
    let(:options) { [nil, { token_type: :param }] }

    it "doesn't add params" do
      request = perform(q: 'hello')
      expect(query_params(request)).to eq('q' => 'hello')
    end

    it "doesn't add headers" do
      expect(auth_header(perform)).to be_nil
    end

    it 'creates header for explicit token' do
      request = perform(q: 'hello', access_token: 'abc123')
      expect(query_params(request)).to eq('q' => 'hello', 'access_token' => 'abc123')
      expect(auth_header(request)).to eq(%(Token token="abc123"))
    end

    context 'bearer token type configured' do
      let(:options) { [nil, { token_type: :bearer }] }

      it "doesn't add headers" do
        expect(auth_header(perform)).to be_nil
      end

      it 'adds header for explicit token' do
        request = perform(q: 'hello', access_token: 'abc123')
        expect(auth_header(request)).to eq(%(Bearer abc123))
      end
    end
  end

  context 'default token configured' do
    let(:options) { ['XYZ', { token_type: :param }] }

    it 'adds token param' do
      expect(query_params(perform(q: 'hello'))).to eq('q' => 'hello', 'access_token' => 'XYZ')
    end

    it 'adds token header' do
      expect(auth_header(perform)).to eq(%(Token token="XYZ"))
    end

    it 'overrides default with explicit token' do
      request = perform(q: 'hello', access_token: 'abc123')
      expect(query_params(request)).to eq('q' => 'hello', 'access_token' => 'abc123')
      expect(auth_header(request)).to eq(%(Token token="abc123"))
    end

    it 'clears default with empty explicit token' do
      request = perform(q: 'hello', access_token: nil)
      expect(query_params(request).fetch('access_token')).to_not eq('XYZ')
      expect(auth_header(request)).to be_nil
    end

    context 'bearer token type configured' do
      let(:options) { ['XYZ', { token_type: :bearer }] }

      it 'adds token header' do
        expect(auth_header(perform)).to eq(%(Bearer XYZ))
      end

      it 'overrides default with explicit token' do
        request = perform(q: 'hello', access_token: 'abc123')
        expect(auth_header(request)).to eq(%(Bearer abc123))
      end

      it 'clears default with empty explicit token' do
        request = perform(q: 'hello', access_token: nil)
        expect(auth_header(request)).to be_nil
      end
    end
  end

  context 'existing Authorization header' do
    let(:options) { ['XYZ', { token_type: :param }] }
    subject { perform({ q: 'hello' }, 'Authorization' => 'custom') }

    it 'adds token param' do
      expect(query_params(subject)).to eq('q' => 'hello', 'access_token' => 'XYZ')
    end

    it "doesn't override existing header" do
      expect(auth_header(subject)).to eq('custom')
    end

    context 'bearer token type configured' do
      let(:options) { ['XYZ', { token_type: :bearer }] }
      subject { perform({ q: 'hello' }, 'Authorization' => 'custom') }

      it "doesn't override existing header" do
        expect(auth_header(subject)).to eq('custom')
      end
    end
  end

  context 'custom param name configured' do
    let(:options) { ['XYZ', { token_type: :param, param_name: :oauth }] }

    it 'adds token param' do
      expect(query_params(perform)).to eq('oauth' => 'XYZ')
    end

    it 'overrides default with explicit token' do
      request = perform(oauth: 'abc123')
      expect(query_params(request)).to eq('oauth' => 'abc123')
      expect(auth_header(request)).to eq(%(Token token="abc123"))
    end
  end

  context 'options without token configuration' do
    let(:options) { [{ token_type: :param, param_name: :oauth }] }

    it "doesn't add param" do
      expect(query_params(perform)).to be_empty
    end

    it 'overrides default with explicit token' do
      expect(query_params(perform(oauth: 'abc123'))).to eq('oauth' => 'abc123')
    end
  end

  context 'invalid param name configured' do
    let(:options) { ['XYZ', { token_type: :param, param_name: nil }] }

    it 'raises error' do
      expect { make_app }.to raise_error(ArgumentError, ":param_name can't be blank")
    end
  end
end
