# frozen_string_literal: true

describe 'Api::V2::Resource::ServiceAccounts' do
  include_context 'bearer authentication'
  let!(:create_member_permission) do
    create :permission,
           role: 'member'
  end
  let!(:create_service_account_permission) do
    create :permission,
           role: 'service_account'
  end
  let!(:create_admin_permission) do
    create :permission,
           role: 'admin'
  end
  let!(:test_user) { create(:user, otp: otp_enabled, role: 'member') }
  let!(:service_account) { create(:service_account, user: test_user) }
  let(:otp_enabled) { true }
  let!(:first_api_key) { create :api_key, key_holder_account: service_account }
  let!(:second_api_key) { create :api_key, key_holder_account: service_account }
  let(:valid_otp_code) { '1357' }
  let(:invalid_otp_code) { '1234' }
  let(:otp_code) { valid_otp_code }

  before do
    allow(TOTPService).to receive(:validate?)
      .with(test_user.uid, valid_otp_code) { true }
    allow(TOTPService).to receive(:validate?)
      .with(test_user.uid, invalid_otp_code) { false }
    allow(TOTPService).to receive(:validate?)
      .with(test_user.uid, nil) { false }
  end

  describe 'GET /api/v2/resource/service_accounts' do
    let(:do_request) do
      get '/api/v2/resource/service_accounts',
          headers: auth_header
    end

    let(:expected_fields) do
      {
          uid: service_account.uid,
          email: service_account.email,
          owner_id: test_user.id,
          role: service_account.role,
          level: service_account.level,
          state: service_account.state,
      }
    end

    it 'Return service accounts for current user' do
      do_request

      expect(json_body.count).to eq 1
      expect(response.status).to eq(200)
      expect(json_body.first).to include(expected_fields)
    end
  end

  describe 'GET /api/v2/resource/service_accounts/api_keys/' do
    let(:get_params) do
      {
          service_account_uid: service_account.uid,
          ordering: 'asc',
          order_by: 'id'
      }
    end
    let(:post_params) do
      {
        service_account_uid: service_account.uid,
        scope: 'trade',
        algorithm: 'HS256'
      }
    end
    let(:do_request) do
      post '/api/v2/resource/service_accounts/api_keys',
           params: post_params.merge(totp_code: otp_code),
           headers: auth_header
    end
    let(:do_get_request) { get "/api/v2/resource/service_accounts/api_keys", params: get_params, headers: auth_header }
    let(:expected_fields) do
      {
        kid: first_api_key.kid,
        state: first_api_key.state,
        scope: %w[trade]
      }
    end
    let(:totp_code) { valid_otp_code }

    it 'Return api key for current account in ASC order' do
      do_get_request

      expect(json_body.count).to eq 2
      expect(response.status).to eq(200)
      expect(json_body.first).to include(expected_fields)
    end

    it 'Return api key for current account in DESC order' do
      get_params.merge!(ordering: 'desc')
      do_get_request

      expect(json_body.count).to eq 2
      expect(response.status).to eq(200)
      expect(json_body.second).to include(expected_fields)
    end

    context 'when invalid ordering' do
      it 'renders an error' do
        get_params.merge!(ordering: 'resc')
        do_get_request

        expect(response.status).to eq(422)
        expect_body.to eq(errors: ["resource.service_accounts.invalid_ordering"])
      end
    end

    context 'when invalid order_by' do
      it 'renders an error' do
        get_params.merge!(order_by: 'invalid')
        do_get_request

        expect(response.status).to eq(422)
        expect_body.to eq(errors: ["resource.service_accounts.invalid_attribute"])
      end
    end

    context 'when otp is not enabled' do
      let(:otp_enabled) { false }

      it 'renders an error' do
        do_request
        expect(response.status).to eq(400)
        expect_body.to eq(errors: ["resource.service_accounts.2fa_disabled"])
      end
    end

    context 'when code is invalid' do
      let(:otp_code) { invalid_otp_code }

      it 'renders an error' do
        do_request
        expect(response.status).to eq(422)
        expect_body.to eq(errors: ["resource.service_accounts.invalid_totp"])
      end
    end
  end

  describe 'POST /api/v2/resource/api_keys' do
    let(:do_request) do
      post '/api/v2/resource/api_keys',
           params: params.merge(totp_code: otp_code),
           headers: auth_header
    end
    let(:otp_code) { valid_otp_code }

    context 'when fields are valid' do
      let(:params) do
        {
          scope: 'trade',
          algorithm: 'HS256'
        }
      end
      let(:expected_fields) do
        {
          state: 'active',
          scope: params[:scope].split(','),
          algorithm: 'HS256'
        }
      end

      it 'Create an api key' do
        expect { do_request }.to change { APIKey.count }.by(1)
        expect(response.status).to eq(201)
        expect_body.to include(expected_fields)
      end

      it 'does not create api key if vault is down' do
        allow(Vault::Rails).to receive(:encrypt).and_raise(Vault::VaultError)
        expect { do_request }.not_to change { APIKey.count }
        expect(response.status).to eq(422)
        expect_body.to eq(errors: ["api_key.could_not_save_secret"])
      end

      context 'when otp is not enabled' do
        let(:otp_enabled) { false }

        it 'renders an error' do
          do_request
          expect(response.status).to eq(400)
          expect_body.to eq(errors: ["resource.api_key.2fa_disabled"])
        end
      end

      context 'when code is invalid' do
        let(:otp_code) { invalid_otp_code }

        it 'renders an error' do
          do_request
          expect(response.status).to eq(422)
          expect_body.to eq(errors: ["resource.api_key.invalid_totp"])
        end
      end

      context 'when algorithm is invalid' do
        it 'renders an error' do
          params[:algorithm] = 'kek'
          do_request
          expect_body.to eq(errors: ["kid.blank", "algorithm.inclusion"])
        end
      end
    end
  end

  describe 'PUT /api/v2/resource/service_accounts/api_keys/:kid' do
    let(:do_request) do
      put "/api/v2/resource/service_accounts/api_keys/#{first_api_key.kid}", params: params.merge(totp_code: otp_code),
                                               headers: auth_header
    end
    let(:otp_code) { valid_otp_code }
    context 'when valid fields' do
      let(:params) do
        {
          service_account_uid: service_account.uid,
          state: 'inactive',
          scope: 'sell',
          algorithm: 'HS256'
        }
      end

      it 'Updates a state' do
        expect { do_request }.to change { first_api_key.reload.state }
          .from('active').to('inactive')
        expect(response.status).to eq(200)
      end

      it 'Updates a scope' do
        expect { do_request }.to change { first_api_key.reload.scope }
          .from(['trade']).to(['sell'])
        expect(response.status).to eq(200)
      end

      context 'when otp is not enabled' do
        let(:otp_enabled) { false }

        it 'renders an error' do
          do_request
          expect(response.status).to eq(400)
          expect_body.to eq(errors: ["resource.service_accounts.2fa_disabled"])
        end
      end

      context 'when code is invalid' do
        let(:otp_code) { invalid_otp_code }

        it 'renders an error' do
          do_request
          expect(response.status).to eq(422)
          expect_body.to eq(errors: ["resource.service_accounts.invalid_totp"])
        end
      end
    end
  end

  describe 'DELETE /api/v2/resource/service_accounts/api_keys/:uid' do
    let(:do_request) do
      delete "/api/v2/resource/service_accounts/api_keys/#{first_api_key.kid}?totp_code=#{otp_code}&service_account_uid=#{service_account.uid}",
             headers: auth_header
    end
    let(:otp_code) { valid_otp_code }

    it 'Removes an api key' do
      do_request
      expect(response.status).to eq(204)
    end

    context 'when otp is not enabled' do
      let(:otp_enabled) { false }

      it 'renders an error' do
        do_request
        expect(response.status).to eq(400)
        expect_body.to eq(errors: ["resource.service_accounts.2fa_disabled"])
      end
    end

    context 'when code is invalid' do
      let(:otp_code) { invalid_otp_code }

      it 'renders an error' do
        do_request
        expect(response.status).to eq(422)
        expect_body.to eq(errors: ["resource.service_accounts.invalid_totp"])
      end
    end
  end
end
