# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later
require 'rails_helper'
describe InsertCard do
  describe'.with_stripe' do

  let(:stripe_helper) { StripeMock.create_test_helper }

  let(:stripe_card_token) { StripeMock.generate_card_token(last4: '9191', exp_year:2011)}
  let(:default_card_attribs) {
    {
      created_at: Time.now,
      updated_at: Time.now,
      profile_id: nil,
      status: nil,
      inactive: nil,
      deleted: nil,
      expiration_month: nil,
      expiration_year: nil,
      email:nil,
      supporter_id: nil
    }
  }

  let(:nonprofit) { force_create(:nonprofit)}
  let(:user) { user = force_create(:user)

    force_create(:role, name: :nonprofit_admin, host: nonprofit, user: user)
    user
  }

  around(:each) {|example|
    Timecop.freeze(2020, 5, 4) do
      StripeMock.start
        example.run
      StripeMock.stop
    end
  }

  it 'params are invalid' do
    ret = InsertCard::with_stripe({ })
    expect(ret[:status]).to eq(:unprocessable_entity)
    expect(ret[:json][:error]).to start_with('Validation error')
    expect(ret[:json][:errors].length).to be(9)

    expect_validation_errors(ret[:json][:errors], [ {key: 'holder_id', name: :required},
      {key: 'holder_type', name: 'included_in'},
      {key: 'holder_type', name: 'required'},
      {key: 'stripe_card_id', name: 'required'},
      {key: 'stripe_card_id', name: 'not_blank'},
      {key: 'stripe_card_token', name: 'required'},
      {key: 'stripe_card_token', name: 'not_blank'},
      {key: 'name', name: 'required'},
      {key: 'name', name: 'not_blank'}
    ])

  end

  describe 'for nonprofits' do


    
    
    let(:supporter) { nonprofit.supporters.first }
    it 'nonprofit doesn\'t exist' do
      ret = InsertCard::with_stripe({:holder_id =>  3, :holder_type => 'Nonprofit', :stripe_card_id => 'card_fafjeht', :stripe_card_token => stripe_card_token, :name => 'name'})
      expect(ret[:status]).to eq(:unprocessable_entity)
      expect(ret[:json][:error]).to include("Sorry, you need to provide a nonprofit or supporter")
    end

    it 'should properly add nonprofit card when no card exists' do
      stripe_customer = nil
      expect(Stripe::Customer).to receive(:create).and_wrap_original{|m, *args| stripe_customer = m.call(*args); stripe_customer}
      card_data = { :holder_type => 'Nonprofit', :holder_id => nonprofit.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token, :name => "card_name" }
      orig_card = nonprofit.active_card
      expect(orig_card).to be_nil
      card_ret = InsertCard::with_stripe(card_data);
      nonprofit.reload
      card = nonprofit.active_card

      compare_card_returned_to_real(card_ret, card)

      expected_card = {
          id: card.id,
          name: 'card_name',
          stripe_card_token: stripe_card_token,
          stripe_card_id: 'card_88888',
          holder_type: 'Nonprofit',
          holder_id: nonprofit.id,
          stripe_customer_id: stripe_customer['id']
      }.merge(default_card_attribs).with_indifferent_access

      expect(card.attributes).to eq expected_card

      

      expect(Card.where('holder_id = ? and holder_type = ?', nonprofit.id, 'Nonprofit').count).to eq(1)

      customer = verify_cust_added_np(card.stripe_customer_id, nonprofit.id)
      expect(customer.sources.count).to eq(1)
      expect(customer.sources.data[0].object).to eq('card')
      expect(customer.sources.data[0].last4).to eq('9191')
      expect(customer.sources.data[0].exp_year).to eq(2011)
    end

    it 'invalid params get ignored' do

      stripe_customer = nil
      expect(Stripe::Customer).to receive(:create).and_wrap_original{|m, *args| stripe_customer = m.call(*args); stripe_customer}
        card_data = { :holder_type => 'Nonprofit', :holder_id => nonprofit.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token,
                      :name => "card_name", :created_at =>  DateTime.new(0), :updated_at => DateTime.new(0), :inactive => true}

        card_ret = InsertCard::with_stripe(card_data);

        nonprofit.reload
        card = Card.find(card_ret[:json][:id])

        expect(nonprofit.active_card).to eq card
        compare_card_returned_to_real(card_ret, card)


        expected_card = {
            id: card.id,
            name: 'card_name',
            stripe_card_token: stripe_card_token,
            stripe_card_id: 'card_88888',
            holder_type: 'Nonprofit',
            holder_id: nonprofit.id,
          stripe_customer_id: stripe_customer['id']
        }.merge(default_card_attribs).with_indifferent_access

        expect(card.attributes).to eq expected_card

    end


    describe 'card exists' do
      before(:each) {
        @first_card_tok = StripeMock.generate_card_token(:last4 => '9999', :exp_year => '2122')
        @stripe_customer = Stripe::Customer.create()
        @stripe_customer.sources.create({token: @first_card_tok})


      }

      it 'should properly add nonprofit card and make old inactive' do
        stripe_customer = nil
        expect(Stripe::Customer).to receive(:create).and_wrap_original{|m, *args| stripe_customer = m.call(*args); stripe_customer}
          first_card = nonprofit.create_active_card(:stripe_card_id => 'fake mcfake', :stripe_card_token => @first_card_tok, :name=> 'fake name')

          card_data = { :holder_type => 'Nonprofit', :holder_id => nonprofit.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token, :name => "card_name" }
          card_ret = InsertCard::with_stripe(card_data);


          nonprofit.reload
          card = nonprofit.active_card

          compare_card_returned_to_real(card_ret, card)

          expected_card = {
              id: card.id,
              name: 'card_name',
              stripe_card_token: stripe_card_token,
              stripe_card_id: 'card_88888',
              holder_type: 'Nonprofit',
              holder_id: nonprofit.id,
              stripe_customer_id: stripe_customer['id']
          }.merge(default_card_attribs).with_indifferent_access

          expect(card.attributes).to eq expected_card
          expect(Card.where('holder_id = ? and holder_type = ?', nonprofit.id, 'Nonprofit').count).to eq(2)
          expect(Card.where('holder_id = ? and holder_type = ? and inactive != ?', nonprofit.id, 'Nonprofit', false).count).to eq(1)

          customer = verify_cust_added_np(card.stripe_customer_id, nonprofit.id)

          expect(customer.sources.count).to eq(1)
          expect(customer.sources.data.any?{|s| s.object == 'card' && s.last4 == '9191' && s.exp_year == 2011}).to eq(true)

          #verify the original card didn't change
          expect(nonprofit.cards.find(first_card.id).attributes.select{|k,_| k != 'inactive'}).to eq first_card.attributes.select{|k, _| k != 'inactive'}

          expect(nonprofit.cards.find(first_card.id).inactive).to eq true

      end
    end

    it 'handle card errors' do
      StripeMock.prepare_error(Stripe::CardError.new('card error', nil, nil, 300,{},{error: {message: 'a message'}}), :new_customer)
      card_data = { :holder_type => 'Nonprofit', :holder_id => nonprofit.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token, :name => "card_name" }

      card_ret = InsertCard::with_stripe(card_data);

      expect(card_ret[:status]).to be :unprocessable_entity
      expect(card_ret[:json][:error]).to start_with('Oops!')
    end

    it 'handle stripe errors' do
      StripeMock.prepare_error(Stripe::StripeError.new('card error', nil, nil), :new_customer)
      card_data = { :holder_type => 'Nonprofit', :holder_id => nonprofit.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token, :name => "card_name" }

      card_ret = InsertCard::with_stripe(card_data);

      expect(card_ret[:status]).to eq :unprocessable_entity
      expect(card_ret[:json][:error]).to start_with('Oops!')
    end


    def verify_cust_added_np(stripe_customer_id, holder_id)
      verify_cust_added(stripe_customer_id, holder_id, 'Nonprofit')
    end


  end

  describe 'for supporter' do
    let(:supporter) {force_create(:supporter, nonprofit: nonprofit)}
    let(:event) {
      force_create(:event, nonprofit: nonprofit, end_datetime: Time.now.since(1.day))}
    let(:user_not_from_nonprofit) {force_create(:user)}
    def verify_cust_added_supporter(stripe_customer_id, holder_id)
      verify_cust_added(stripe_customer_id, holder_id, 'Supporter')
    end

    def verify_supporter_source_token(source_token, card)
      verify_source_token(source_token, card, 1, Time.now.since(20.minutes))
    end

    def verify_event_source_token(source_token, card, event)
      verify_source_token(source_token, card, 20, event.end_datetime.since(20.days), event)
    end

    context 'card exists' do

      let(:supporter) {create(:supporter, :has_a_card, nonprofit:nonprofit)}


      it 'should properly add supporter card' do
        expect(supporter.cards.count).to eq(1)
        stripe_customer = nil
        expect(Stripe::Customer).to receive(:create).and_wrap_original{|m, *args| stripe_customer = m.call(*args); stripe_customer}
        card_data = { :holder_type => 'Supporter', :holder_id => supporter.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token, :name => "card_name" }
        orig_card = supporter.cards.first
        card_ret = InsertCard::with_stripe(card_data);
        supporter.reload
        card = supporter.cards.where('cards.name = ?', 'card_name').first
        compare_card_returned_to_real(card_ret, card, card_ret[:json]['token'])

        expected_card = {
          id: card.id,
          name: 'card_name',
          stripe_card_token: stripe_card_token,
          stripe_card_id: 'card_88888',
          holder_id: supporter.id,
          holder_type: 'Supporter',
          stripe_customer_id: stripe_customer['id'],
        }.merge(default_card_attribs).with_indifferent_access

        expect(card.attributes).to eq expected_card

        expect(supporter.cards.count).to eq(2)

        expect(Card.where('holder_id = ? and holder_type = ?', supporter.id, 'Supporter').count).to eq(2)
        expect(Card.where('holder_id = ? and holder_type = ? and inactive != ?', supporter.id, 'Supporter', false).count).to eq(0)

        expect(supporter.cards.find(orig_card.id)).to eq(orig_card)

        verify_cust_added_supporter(card.stripe_customer_id, supporter.id)

        verify_supporter_source_token(card_ret[:json]['token'], card)
      end

      it 'should properly add card for event' do
        expect(supporter.cards.count).to eq(1)
        stripe_customer = nil
        expect(Stripe::Customer).to receive(:create).and_wrap_original{|m, *args| stripe_customer = m.call(*args); stripe_customer}
        card_data = { :holder_type => 'Supporter', :holder_id => supporter.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token, :name => "card_name" }
        orig_card = supporter.cards.first
        card_ret = InsertCard::with_stripe(card_data, nil, event.id, user);
        supporter.reload
        card = supporter.cards.where('cards.name = ?', 'card_name').first
        compare_card_returned_to_real(card_ret, card, card_ret[:json]['token'])

        expected_card = {
            id: card.id,
            name: 'card_name',
            stripe_card_token: stripe_card_token,
            stripe_card_id: 'card_88888',
            holder_id: supporter.id,
            holder_type: 'Supporter',
            stripe_customer_id: stripe_customer['id'],
        }.merge(default_card_attribs).with_indifferent_access

        expect(card.attributes).to eq expected_card

        expect(supporter.cards.count).to eq(2)

        expect(Card.where('holder_id = ? and holder_type = ?', supporter.id, 'Supporter').count).to eq(2)
        expect(Card.where('holder_id = ? and holder_type = ? and inactive != ?', supporter.id, 'Supporter', false).count).to eq(0)

        expect(supporter.cards.find(orig_card.id)).to eq(orig_card)

        verify_cust_added_supporter(card.stripe_customer_id, supporter.id)


        verify_event_source_token(card_ret[:json]['token'], card, event)
      end
    end

    context 'card doesnt exist' do


      it 'invalid params get ignored' do
          stripe_customer = nil
          expect(Stripe::Customer).to receive(:create).and_wrap_original{|m, *args| stripe_customer = m.call(*args); stripe_customer}
          card_data = { :holder_type => 'Supporter', :holder_id => supporter.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token,
                        :name => "card_name", :created_at =>  DateTime.new(0), :updated_at => DateTime.new(0), :inactive => true}

          card_ret = InsertCard.with_stripe(card_data);


          card = Card.find(card_ret[:json][:id])

          supporter.reload

          compare_card_returned_to_real(card_ret, card, card_ret[:json]['token'])


          expected_card = {
              id: card.id,
              holder_type: 'Supporter',
              holder_id: supporter.id,
              stripe_card_token: stripe_card_token,
              name: 'card_name',
              stripe_card_id: 'card_88888',
              stripe_customer_id: stripe_customer['id']

          }.merge(default_card_attribs).with_indifferent_access

          expect(card.attributes).to eq expected_card

          verify_supporter_source_token(card_ret[:json]['token'], card)


      end

      it 'should properly add supporter card when no card exist' do
        stripe_customer = nil
        expect(Stripe::Customer).to receive(:create).and_wrap_original{|m, *args| stripe_customer = m.call(*args); stripe_customer}

        card_data = { :holder_type => 'Supporter', :holder_id => supporter.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token, :name => "card_name" }
        card_ret = InsertCard::with_stripe(card_data);
        supporter.reload
        card = supporter.cards.where('cards.name = ?', 'card_name').first
        compare_card_returned_to_real(card_ret, card, card_ret[:json]['token'])
        expected_card = {
            id: card.id,
            name: 'card_name',
            stripe_card_id: 'card_88888',
            stripe_card_token: stripe_card_token,
            stripe_customer_id: stripe_customer['id'],
            holder_type: 'Supporter',
            holder_id: supporter.id
        }.merge(default_card_attribs).with_indifferent_access

        expect(card.attributes).to eq expected_card

        expect(supporter.cards.count).to eq(1)

        expect(Card.where('holder_id = ? and holder_type = ?', supporter.id, 'Supporter').count).to eq(1)
        expect(Card.where('holder_id = ? and holder_type = ? and inactive != ?', supporter.id, 'Supporter', false).count).to eq(0)
        verify_cust_added_supporter(card.stripe_customer_id, supporter.id)

        verify_supporter_source_token(card_ret[:json]['token'], card)
      end

      it 'should properly add card for event' do

        stripe_customer = nil
        expect(Stripe::Customer).to receive(:create).and_wrap_original{|m, *args| stripe_customer = m.call(*args); stripe_customer}
        card_data = { :holder_type => 'Supporter', :holder_id => supporter.id, :stripe_card_id => 'card_88888', :stripe_card_token => stripe_card_token, :name => "card_name" }
        card_ret = InsertCard::with_stripe(card_data, nil, event.id, user);
        supporter.reload
        card = supporter.cards.where('cards.name = ?', 'card_name').first
        compare_card_returned_to_real(card_ret, card, card_ret[:json]['token'])

        expected_card = {
            id: card.id,
            name: 'card_name',
            stripe_card_token: stripe_card_token,
            stripe_card_id: 'card_88888',
            holder_id: supporter.id,
            holder_type: 'Supporter',
            stripe_customer_id: stripe_customer['id'],
        }.merge(default_card_attribs).with_indifferent_access

        expect(card.attributes).to eq expected_card

        expect(supporter.cards.count).to eq(1)

        expect(Card.where('holder_id = ? and holder_type = ?', supporter.id, 'Supporter').count).to eq(1)
        expect(Card.where('holder_id = ? and holder_type = ? and inactive != ?', supporter.id, 'Supporter', false).count).to eq(0)

        verify_cust_added_supporter(card.stripe_customer_id, supporter.id)

        verify_event_source_token(card_ret[:json]['token'], card, event)
      end
    end



    it 'should return proper error when no supporter exists' do
      ret = InsertCard::with_stripe({:holder_id =>  5555555, :holder_type => 'Supporter', :stripe_card_id => 'card_fafjeht', :stripe_card_token => stripe_card_token, :name => 'name'})
      expect(ret[:status]).to eq(:unprocessable_entity)
      expect(ret[:json][:error]).to include("Sorry, you need to provide a nonprofit or supporter")
    end

    it 'should return proper error when you try to add using an event with unauthorized user' do
      ret = InsertCard::with_stripe({holder_id: supporter.id, holder_type: 'Supporter', stripe_card_id: 'card_fafjeht', stripe_card_token: stripe_card_token, name: 'name'}, nil,  event.id,  user_not_from_nonprofit)

      expect(ret[:json][:error]).to eq "You're not authorized to perform that action"
      expect(ret[:status]).to eq (:unauthorized)
    end

    it 'should return proper error when an invalid event_id is provided' do
      ret = InsertCard.with_stripe({holder_id: supporter.id, holder_type: 'Supporter', stripe_card_id: 'card_fafjeht', stripe_card_token: stripe_card_token, name: 'name'}, nil, 55555,  user_not_from_nonprofit)
      expect(ret).to eq({ status: :unprocessable_entity, json: {error: 'Oops! There was an error: 55555 is not a valid event'}})
    end

    it 'should return proper error when event doesnt match the supporters nonprofit' do
      supporter2 = force_create(:supporter)
      ret = InsertCard.with_stripe({holder_id:  supporter2.id, holder_type: 'Supporter', stripe_card_id: 'card_fafjeht', stripe_card_token: stripe_card_token, name: 'name'}, nil, event.id,  user_not_from_nonprofit)
      expect(ret).to eq({ status: :unprocessable_entity, json: {error: "Oops! There was an error: Event #{event.id} is not for the same nonprofit as supporter #{supporter2.id}"}})
    end


  end
  def compare_card_returned_to_real(card_ret, db_card, token=nil)
    expect(card_ret[:status]).to eq(:ok)

    expected_json = db_card.attributes
    expected_json.merge!({'token' => token})
    if token
      expect(token).to match(UUID::Regex)
    end
    expect(card_ret[:json]).to eq(expected_json)
  end

  def verify_cust_added(stripe_customer_id, holder_id, holder_type)
    customer = Stripe::Customer.retrieve(stripe_customer_id)
    # does the customer exist? Was it set properly? Was the card set properly
    expect(customer).to_not be_nil
    expected_metadata = {
        holder_id: holder_id,
        holder_type: holder_type,
        cardholders_name: nil
    }

    expect(customer.metadata.to_hash).to eq expected_metadata
    customer
  end

  def verify_source_token(source_token, card, max_uses, expiration_time, event=nil)
    tok = SourceToken.where('token = ?', source_token).first
    expected = {
      created_at: Time.now,
      updated_at: Time.now,
      tokenizable_id: card.id,
      tokenizable_type: 'Card',
      max_uses: max_uses,
      total_uses: 0,
      expiration: expiration_time,
      event_id: event ? event.id : nil,
      token: source_token
    }.with_indifferent_access

    expect(tok.attributes).to eq expected
  end

  end
end