require 'rails_helper'
require 'support/flow_policy_helper'

RSpec.describe 'Idv::FlowPolicy' do
  include Rails.application.routes.url_helpers
  include FlowPolicyHelper

  let(:user) { create(:user) }
  let(:is_enhanced_ipp) { false }

  let(:user_session) { { 'idv/in_person' => {} } }

  let(:idv_session) do
    Idv::Session.new(
      user_session: user_session,
      current_user: user,
      service_provider: nil,
    )
  end

  let(:user_phone_confirmation_session) { nil }
  let(:has_gpo_pending_profile) { nil }

  subject { Idv::FlowPolicy.new(idv_session: idv_session, user: user) }

  context '#controller_allowed?' do
    it 'allows the welcome step' do
      expect(subject.controller_allowed?(controller: Idv::WelcomeController)).to be true
    end
  end

  context '#undo_future_steps_from_controller!' do
    context 'user is on verify_info step' do
      before do
        idv_session.welcome_visited = true
        idv_session.document_capture_session_uuid = SecureRandom.uuid

        idv_session.idv_consent_given_at = Time.zone.now
        idv_session.skip_hybrid_handoff = true

        idv_session.flow_path = 'standard'
        idv_session.phone_for_mobile_flow = '201-555-1212'

        idv_session.pii_from_doc = Pii::StateId.new(**Idp::Constants::MOCK_IDV_APPLICANT)
        idv_session.doc_auth_vendor = 'test_vendor'
        idv_session.had_barcode_read_failure = true
        idv_session.had_barcode_attention_error = true

        idv_session.ssn = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN[:ssn]

        idv_session.address_edited = true
      end

      it 'clears future steps when user goes back and submits welcome' do
        subject.undo_future_steps_from_controller!(controller: Idv::WelcomeController)

        expect(idv_session.welcome_visited).not_to be_nil
        expect(idv_session.document_capture_session_uuid).not_to be_nil

        expect(idv_session.idv_consent_given_at).to be_nil
        expect(idv_session.skip_hybrid_handoff).to be_nil

        expect(idv_session.flow_path).to be_nil
        expect(idv_session.phone_for_mobile_flow).to be_nil

        expect(idv_session.pii_from_doc).to be_nil
        expect(idv_session.doc_auth_vendor).to be_nil
        expect(idv_session.had_barcode_read_failure).to be_nil
        expect(idv_session.had_barcode_attention_error).to be_nil

        expect(idv_session.ssn).to be_nil

        expect(idv_session.address_edited).to be_nil
      end
    end

    context 'user is on enter password step' do
      before do
        idv_session.welcome_visited = true
        idv_session.document_capture_session_uuid = SecureRandom.uuid

        idv_session.idv_consent_given_at = Time.zone.now
        idv_session.skip_hybrid_handoff = true

        idv_session.flow_path = 'standard'
        idv_session.phone_for_mobile_flow = '201-555-1212'

        idv_session.pii_from_doc = nil
        idv_session.had_barcode_read_failure = true
        idv_session.had_barcode_attention_error = true
        idv_session.doc_auth_vendor = 'test_vendor'

        idv_session.ssn = nil

        idv_session.address_edited = true

        idv_session.verify_info_step_document_capture_session_uuid = SecureRandom.uuid
        idv_session.threatmetrix_review_status = 'pass'
        idv_session.resolution_successful = true
        idv_session.applicant = Idp::Constants::MOCK_IDV_APPLICANT_WITH_SSN.dup

        idv_session.vendor_phone_confirmation = true
        idv_session.address_verification_mechanism = 'phone'
        idv_session.idv_phone_step_document_capture_session_uuid = SecureRandom.uuid
        idv_session.user_phone_confirmation_session = { phone: '201-555-1212' }
        idv_session.previous_phone_step_params = '201-555-1111'

        idv_session.user_phone_confirmation = true
      end

      it 'does clear steps after ssn when user submits ssn' do
        subject.undo_future_steps_from_controller!(controller: Idv::SsnController)

        expect(idv_session.address_edited).to be_nil

        expect(idv_session.verify_info_step_document_capture_session_uuid).to be_nil
        expect(idv_session.threatmetrix_review_status).to be_nil
        expect(idv_session.resolution_successful).to be_nil
        expect(idv_session.applicant).to be_nil

        expect(idv_session.vendor_phone_confirmation).to be_nil
        expect(idv_session.address_verification_mechanism).to be_nil
        expect(idv_session.idv_phone_step_document_capture_session_uuid).to be_nil
        expect(idv_session.user_phone_confirmation_session).to be_nil
        expect(idv_session.previous_phone_step_params).to be_nil

        expect(idv_session.user_phone_confirmation).to be_nil
      end

      it 'does not clear earlier steps when user goes back and submits ssn' do
        expect do
          subject.undo_future_steps_from_controller!(controller: Idv::SsnController)
        end.not_to change {
          idv_session.welcome_visited
          idv_session.document_capture_session_uuid
          idv_session.doc_auth_vendor
          idv_session.idv_consent_given_at
          idv_session.idv_consent_given?
          idv_session.skip_hybrid_handoff

          idv_session.flow_path
          idv_session.phone_for_mobile_flow

          idv_session.had_barcode_read_failure
          idv_session.had_barcode_attention_error
        }
      end
    end
  end

  context 'each step in the flow' do
    before do
      allow(Idv::PhoneConfirmationSession).to receive(:from_h)
        .with(user_phone_confirmation_session).and_return(user_phone_confirmation_session)
      allow(user).to receive(:gpo_pending_profile?).and_return(has_gpo_pending_profile)
    end

    context 'empty session' do
      it 'returns welcome' do
        expect(subject.info_for_latest_step.key).to eq(:welcome)
      end
    end

    context 'preconditions for agreement are present' do
      it 'returns agreement' do
        stub_up_to(:welcome, idv_session: idv_session)

        expect(subject.info_for_latest_step.key).to eq(:agreement)
        expect(subject.controller_allowed?(controller: Idv::AgreementController)).to be
        expect(subject.controller_allowed?(controller: Idv::HybridHandoffController)).not_to be
      end
    end

    context 'preconditions for hybrid_handoff are present' do
      it 'returns hybrid_handoff' do
        stub_up_to(:agreement, idv_session: idv_session)

        expect(subject.info_for_latest_step.key).to eq(:hybrid_handoff)
        expect(subject.controller_allowed?(controller: Idv::HybridHandoffController)).to be
        expect(subject.controller_allowed?(controller: Idv::DocumentCaptureController)).not_to be
      end
    end

    context 'preconditions for choose_id_type are present' do
      it 'returns choose_id_type' do
        stub_up_to(:hybrid_handoff, idv_session: idv_session)

        expect(subject.info_for_latest_step.key).to eq(:choose_id_type)
        expect(subject.controller_allowed?(controller: Idv::ChooseIdTypeController)).to be
        expect(subject.controller_allowed?(controller: Idv::DocumentCaptureController)).not_to be
      end
    end

    context 'preconditions for document_capture are present' do
      it 'returns document_capture' do
        stub_up_to(:choose_id_type, idv_session: idv_session)

        expect(subject.info_for_latest_step.key).to eq(:document_capture)
        expect(subject.controller_allowed?(controller: Idv::DocumentCaptureController)).to be
        expect(subject.controller_allowed?(controller: Idv::SsnController)).not_to be
      end
    end

    context 'preconditions for link_sent are present' do
      it 'returns link_sent' do
        stub_up_to(:hybrid_handoff, idv_session: idv_session)
        idv_session.flow_path = 'hybrid'

        expect(subject.info_for_latest_step.key).to eq(:link_sent)
        expect(subject.controller_allowed?(controller: Idv::LinkSentController)).to be
        expect(subject.controller_allowed?(controller: Idv::SsnController)).not_to be
      end
    end

    context 'preconditions for ssn are present' do
      before do
        stub_up_to(:document_capture, idv_session: idv_session)
      end

      it 'returns ssn for standard flow' do
        expect(subject.info_for_latest_step.key).to eq(:ssn)
        expect(subject.controller_allowed?(controller: Idv::SsnController)).to be
        expect(subject.controller_allowed?(controller: Idv::VerifyInfoController)).not_to be
      end

      it 'returns ssn for hybrid flow' do
        idv_session.flow_path = 'hybrid'
        expect(subject.info_for_latest_step.key).to eq(:ssn)
        expect(subject.controller_allowed?(controller: Idv::SsnController)).to be
        expect(subject.controller_allowed?(controller: Idv::VerifyInfoController)).not_to be
      end
    end

    context 'preconditions for in_person ssn are present' do
      before do
        stub_up_to(:ipp_address, idv_session: idv_session)
        allow(user).to receive(:has_establishing_in_person_enrollment?).and_return(true)
        idv_session.opted_in_to_in_person_proofing = true
        idv_session.send(:user_session)['idv/in_person'] = {
          pii_from_user: Idp::Constants::MOCK_IDV_APPLICANT_SAME_ADDRESS_AS_ID.dup,
        }
      end

      it 'returns ipp_ssn' do
        expect(subject.info_for_latest_step.key).to eq(:ipp_ssn)
        expect(subject.controller_allowed?(controller: Idv::InPerson::SsnController)).to be
        expect(subject.controller_allowed?(controller: Idv::InPerson::VerifyInfoController))
          .not_to be
      end
    end

    context 'preconditions for verify_info are present' do
      it 'returns verify_info' do
        stub_up_to(:ssn, idv_session: idv_session)
        expect(subject.info_for_latest_step.key).to eq(:verify_info)
        expect(subject.controller_allowed?(controller: Idv::VerifyInfoController)).to be
        expect(subject.controller_allowed?(controller: Idv::PhoneController)).not_to be
      end
    end

    context 'preconditions for ipp_choose_id_type are present' do
      before do
        allow(idv_session).to receive(:in_person_passports_allowed?).and_return(true)
        allow(user).to receive(:has_establishing_in_person_enrollment?).and_return(true)
      end

      it 'returns ipp_choose_id_type' do
        expect(
          subject.controller_allowed?(
            controller: Idv::InPerson::ChooseIdTypeController,
          ),
        ).to be
      end
    end

    context 'preconditions for in_person verify_info are present' do
      before do
        idv_session.opted_in_to_in_person_proofing = true
      end

      it 'returns ipp_verify_info' do
        stub_up_to(:ipp_ssn, idv_session: idv_session)
        allow(user).to receive(:has_establishing_in_person_enrollment?).and_return(true)
        expect(subject.info_for_latest_step.key).to eq(:ipp_verify_info)
        expect(subject.controller_allowed?(controller: Idv::InPerson::VerifyInfoController)).to be
        expect(subject.controller_allowed?(controller: Idv::PhoneController)).not_to be
      end
    end

    context 'preconditions for phone are present' do
      it 'returns phone' do
        stub_up_to(:verify_info, idv_session: idv_session)

        expect(subject.info_for_latest_step.key).to eq(:phone)
        expect(subject.controller_allowed?(controller: Idv::PhoneController)).to be
        expect(subject.controller_allowed?(controller: Idv::OtpVerificationController)).not_to be
      end
    end

    context 'preconditions for otp_verification are present' do
      let(:user_phone_confirmation_session) { { code: 'abcde' } }

      it 'returns otp_verification' do
        stub_up_to(:phone, idv_session: idv_session)

        idv_session.user_phone_confirmation_session = user_phone_confirmation_session

        expect(subject.info_for_latest_step.key).to eq(:otp_verification)
        expect(subject.controller_allowed?(controller: Idv::OtpVerificationController)).to be
        expect(subject.controller_allowed?(controller: Idv::EnterPasswordController)).not_to be
      end
    end

    context 'preconditions for request_letter are present' do
      it 'allows request_letter' do
        stub_up_to(:verify_info, idv_session: idv_session)

        expect(subject.controller_allowed?(controller: Idv::ByMail::RequestLetterController)).to be
        expect(subject.controller_allowed?(controller: Idv::EnterPasswordController)).not_to be
      end
    end

    context 'preconditions for enter_password are present' do
      let(:user_phone_confirmation_session) { { code: 'abcde' } }

      context 'user has a gpo address_verification_mechanism' do
        it 'returns enter_password with gpo verification pending' do
          stub_up_to(:request_letter, idv_session: idv_session)

          expect(subject.info_for_latest_step.key).to eq(:enter_password)
          expect(subject.controller_allowed?(controller: Idv::EnterPasswordController)).to be
          expect(subject.controller_allowed?(controller: Idv::PersonalKeyController)).not_to be
        end
      end

      context 'user passed phone step' do
        it 'returns enter_password' do
          stub_up_to(:otp_verification, idv_session: idv_session)

          expect(subject.info_for_latest_step.key).to eq(:enter_password)
          expect(subject.controller_allowed?(controller: Idv::EnterPasswordController)).to be
          expect(subject.controller_allowed?(controller: Idv::PersonalKeyController)).not_to be
        end
      end
    end

    context 'preconditions for personal_key are present' do
      let(:is_enhanced_ipp) { false }
      let(:password) { 'sekrit phrase' }

      context 'user has a verify by mail pending profile' do
        it 'returns personal_key' do
          stub_up_to(:request_letter, idv_session: idv_session)
          idv_session.gpo_code_verified = true
          idv_session.create_profile_from_applicant_with_password(
            'password', is_enhanced_ipp:, proofing_components: {}
          )

          expect(subject.info_for_latest_step.key).to eq(:personal_key)
          expect(subject.controller_allowed?(controller: Idv::PersonalKeyController)).to be
        end
      end

      context 'user has a newly activated profile' do
        let(:is_enhanced_ipp) { false }

        it 'returns personal_key' do
          stub_up_to(:otp_verification, idv_session: idv_session)
          idv_session.create_profile_from_applicant_with_password(
            'password', is_enhanced_ipp:, proofing_components: {}
          )

          expect(subject.info_for_latest_step.key).to eq(:personal_key)
          expect(subject.controller_allowed?(controller: Idv::PersonalKeyController)).to be
        end
      end
    end
  end
end
