# frozen_string_literal: true

require 'rails_helper'

RSpec.describe SecondFactorManager do
  fab!(:user_second_factor_totp) { Fabricate(:user_second_factor_totp) }
  let(:user) { user_second_factor_totp.user }
  fab!(:another_user) { Fabricate(:user) }

  fab!(:user_second_factor_backup) { Fabricate(:user_second_factor_backup) }
  let(:user_backup) {  user_second_factor_backup.user }

  describe '#totp' do
    it 'should return the right data' do
      totp = nil

      expect do
        totp = another_user.create_totp(enabled: true)
      end.to change { UserSecondFactor.count }.by(1)

      expect(totp.get_totp_object.issuer).to eq(SiteSetting.title)
      expect(totp.get_totp_object.secret).to eq(another_user.reload.user_second_factors.totps.first.data)
    end
  end

  describe '#create_totp' do
    it 'should create the right record' do
      second_factor = another_user.create_totp(enabled: true)

      expect(second_factor.method).to eq(UserSecondFactor.methods[:totp])
      expect(second_factor.data).to be_present
      expect(second_factor.enabled).to eq(true)
    end
  end

  describe '#totp_provisioning_uri' do
    it 'should return the right uri' do
      expect(user.user_second_factors.totps.first.totp_provisioning_uri).to eq(
        "otpauth://totp/#{SiteSetting.title}:#{user.email}?secret=#{user_second_factor_totp.data}&issuer=#{SiteSetting.title}"
      )
    end
  end

  describe '#authenticate_totp' do
    it 'should be able to authenticate a token' do
      freeze_time do
        expect(user.user_second_factors.totps.first.last_used).to eq(nil)

        token = user.user_second_factors.totps.first.get_totp_object.now

        expect(user.authenticate_totp(token)).to eq(true)
        expect(user.user_second_factors.totps.first.last_used).to eq_time(DateTime.now)
        expect(user.authenticate_totp(token)).to eq(false)
      end
    end

    describe 'when token is blank' do
      it 'should be false' do
        expect(user.authenticate_totp(nil)).to eq(false)
        expect(user.user_second_factors.totps.first.last_used).to eq(nil)
      end
    end

    describe 'when token is invalid' do
      it 'should be false' do
        expect(user.authenticate_totp('111111')).to eq(false)
        expect(user.user_second_factors.totps.first.last_used).to eq(nil)
      end
    end
  end

  describe '#totp_enabled?' do
    describe 'when user does not have a second factor record' do
      it 'should return false' do
        expect(another_user.totp_enabled?).to eq(false)
      end
    end

    describe "when user's second factor record is disabled" do
      it 'should return false' do
        user.user_second_factors.totps.first.update!(enabled: false)
        expect(user.totp_enabled?).to eq(false)
      end
    end

    describe "when user's second factor record is enabled" do
      it 'should return true' do
        expect(user.totp_enabled?).to eq(true)
      end
    end

    describe 'when SSO is enabled' do
      it 'should return false' do
        SiteSetting.sso_url = 'http://someurl.com'
        SiteSetting.enable_sso = true

        expect(user.totp_enabled?).to eq(false)
      end
    end

    describe 'when local login is disabled' do
      it 'should return false' do
        SiteSetting.enable_local_logins = false

        expect(user.totp_enabled?).to eq(false)
      end
    end
  end

  context 'backup codes' do
    describe '#generate_backup_codes' do
      it 'should generate and store 10 backup codes' do
        backup_codes = user.generate_backup_codes

        expect(backup_codes.length).to be 10
        expect(user_backup.user_second_factors.backup_codes).to be_present
        expect(user_backup.user_second_factors.backup_codes.pluck(:method).uniq[0]).to eq(UserSecondFactor.methods[:backup_codes])
        expect(user_backup.user_second_factors.backup_codes.pluck(:enabled).uniq[0]).to eq(true)
      end
    end

    describe '#create_backup_codes' do
      it 'should create 10 backup code records' do
        raw_codes = Array.new(10) { SecureRandom.hex(8) }
        backup_codes = another_user.create_backup_codes(raw_codes)

        expect(another_user.user_second_factors.backup_codes.length).to be 10
      end
    end

    describe '#authenticate_backup_code' do
      it 'should be able to authenticate a backup code' do
        backup_code = "iAmValidBackupCode"

        expect(user_backup.authenticate_backup_code(backup_code)).to eq(true)
        expect(user_backup.authenticate_backup_code(backup_code)).to eq(false)
      end

      describe 'when code is blank' do
        it 'should be false' do
          expect(user_backup.authenticate_backup_code(nil)).to eq(false)
        end
      end

      describe 'when code is invalid' do
        it 'should be false' do
          expect(user_backup.authenticate_backup_code("notValidBackupCode")).to eq(false)
        end
      end
    end

    describe '#backup_codes_enabled?' do
      describe 'when user does not have a second factor backup enabled' do
        it 'should return false' do
          expect(another_user.backup_codes_enabled?).to eq(false)
        end
      end

      describe "when user's second factor backup codes have been used" do
        it 'should return false' do
          user_backup.user_second_factors.backup_codes.update_all(enabled: false)
          expect(user_backup.backup_codes_enabled?).to eq(false)
        end
      end

      describe "when user's second factor code is available" do
        it 'should return true' do
          expect(user_backup.backup_codes_enabled?).to eq(true)
        end
      end

      describe 'when SSO is enabled' do
        it 'should return false' do
          SiteSetting.sso_url = 'http://someurl.com'
          SiteSetting.enable_sso = true

          expect(user_backup.backup_codes_enabled?).to eq(false)
        end
      end

      describe 'when local login is disabled' do
        it 'should return false' do
          SiteSetting.enable_local_logins = false

          expect(user_backup.backup_codes_enabled?).to eq(false)
        end
      end
    end
  end
end