# frozen_string_literal: false

require 'spec_helper'

RSpec.describe Spree::CreditCard do
  let(:valid_credit_card_attributes) {
    {
      number: '4111111111111111',
      verification_value: '123',
      month: 12,
      year: Time.zone.now.year + 1
    }
  }

  describe "original specs from Spree" do
    def self.payment_states
      Spree::Payment.state_machine.states.keys
    end

    def stub_rails_env(environment)
      allow(Rails).to receive_messages(env: ActiveSupport::StringInquirer.new(environment))
    end

    let(:credit_card) { described_class.new }

    context "#can_capture?" do
      it "should be true if payment is pending" do
        payment = build_stubbed(:payment, created_at: Time.zone.now)
        allow(payment).to receive(:pending?) { true }
        expect(credit_card.can_capture_and_complete_order?(payment)).to be_truthy
      end

      it "should be true if payment is checkout" do
        payment = build_stubbed(:payment, created_at: Time.zone.now)
        allow(payment).to receive_messages pending?: false,
                                           checkout?: true
        expect(credit_card.can_capture_and_complete_order?(payment)).to be_truthy
      end
    end

    context "#can_void?" do
      it "should be true if payment is not void" do
        payment = build_stubbed(:payment)
        allow(payment).to receive(:void?) { false }
        expect(credit_card.can_void?(payment)).to be_truthy
      end
    end

    context "#can_credit?" do
      it "should be false if payment is not completed" do
        payment = build_stubbed(:payment)
        allow(payment).to receive(:completed?) { false }
        expect(credit_card.can_credit?(payment)).to be_falsy
      end

      it "should be false when order payment_state is not 'credit_owed'" do
        payment = build_stubbed(:payment,
                                order: create(:order, payment_state: 'paid'))
        allow(payment).to receive(:completed?) { true }
        expect(credit_card.can_credit?(payment)).to be_falsy
      end

      it "should be false when credit_allowed is zero" do
        payment = build_stubbed(:payment,
                                order: create(:order, payment_state: 'credit_owed'))
        allow(payment).to receive_messages completed?: true,
                                           credit_allowed: 0

        expect(credit_card.can_credit?(payment)).to be_falsy
      end
    end

    context "#valid?" do
      it "should validate presence of number" do
        credit_card.attributes = valid_credit_card_attributes.except(:number)
        expect(credit_card).not_to be_valid
        expect(credit_card.errors[:number]).to eq ["can't be blank"]
      end

      it "should validate presence of security code" do
        credit_card.attributes = valid_credit_card_attributes.except(:verification_value)
        expect(credit_card).not_to be_valid
        expect(credit_card.errors[:verification_value]).to eq ["can't be blank"]
      end

      it "should validate expiration is not in the past" do
        credit_card.month = 1.month.ago.month
        credit_card.year = 1.month.ago.year
        expect(credit_card).not_to be_valid
        expect(credit_card.errors[:base]).to eq ["has expired"]
      end

      it "does not run expiration in the past validation if month is not set" do
        credit_card.month = nil
        credit_card.year = Time.zone.now.year
        expect(credit_card).not_to be_valid
        expect(credit_card.errors[:base]).to be_blank
      end

      it "does not run expiration in the past validation if year is not set" do
        credit_card.month = Time.zone.now.month
        credit_card.year = nil
        expect(credit_card).not_to be_valid
        expect(credit_card.errors[:base]).to be_blank
      end

      it "does not run expiration in the past validation if year and month are empty" do
        credit_card.year = ""
        credit_card.month = ""
        expect(credit_card).not_to be_valid
        expect(credit_card.errors[:card]).to be_blank
      end

      it "should only validate on create" do
        credit_card.attributes = valid_credit_card_attributes
        credit_card.save
        expect(credit_card).to be_valid
      end
    end

    context "#save" do
      before do
        credit_card.attributes = valid_credit_card_attributes
        credit_card.save!
      end

      let!(:persisted_card) { described_class.find(credit_card.id) }

      it "should not actually store the number" do
        expect(persisted_card.number).to be_blank
      end

      it "should not actually store the security code" do
        expect(persisted_card.verification_value).to be_blank
      end
    end

    context "#number=" do
      it "should strip non-numeric characters from card input" do
        credit_card.number = "6011000990139424"
        expect(credit_card.number).to eq "6011000990139424"

        credit_card.number = "  6011-0009-9013-9424  "
        expect(credit_card.number).to eq "6011000990139424"
      end

      it "should not raise an exception on non-string input" do
        credit_card.number = ({})
        expect(credit_card.number).to be_nil
      end
    end

    context "#associations" do
      it "should be able to access its payments" do
        expect { credit_card.payments.to_a }.not_to raise_error
      end
    end

    context "#to_active_merchant" do
      before do
        credit_card.number = "4111111111111111"
        credit_card.year = Time.zone.now.year
        credit_card.month = Time.zone.now.month
        credit_card.first_name = "Bob"
        credit_card.last_name = "Boblaw"
        credit_card.verification_value = 123
      end

      it "converts to an ActiveMerchant::Billing::CreditCard object" do
        am_card = credit_card.to_active_merchant
        expect(am_card.number).to eq "4111111111111111"
        expect(am_card.year).to eq Time.zone.now.year
        expect(am_card.month).to eq Time.zone.now.month
        expect(am_card.first_name).to eq "Bob"
        am_card.last_name = "Boblaw"
        expect(am_card.verification_value).to eq 123
      end
    end
  end

  describe "formatting the card type for ActiveMerchant" do
    context "#cc_type=" do
      let(:credit_card) { build(:credit_card) }

      it "converts the card type format" do
        credit_card.cc_type = 'mastercard'
        expect(credit_card.cc_type).to eq 'master'

        credit_card.cc_type = 'maestro'
        expect(credit_card.cc_type).to eq 'master'

        credit_card.cc_type = 'amex'
        expect(credit_card.cc_type).to eq 'american_express'

        credit_card.cc_type = 'dinersclub'
        expect(credit_card.cc_type).to eq 'diners_club'

        credit_card.cc_type = 'some_outlandish_cc_type'
        expect(credit_card.cc_type).to eq 'some_outlandish_cc_type'
      end
    end

    context "on save" do
      it "converts the card type format" do
        expect_any_instance_of(described_class).to receive(:reformat_card_type!).
          at_least(:once).and_call_original

        credit_card = described_class.create(
          valid_credit_card_attributes.merge(cc_type: "Master Card")
        )

        expect(credit_card.cc_type).to eq "master"
      end
    end
  end

  describe "setting default credit card for a user" do
    let(:user) { create(:user) }
    let(:onetime_card_attrs) do
      { user:, gateway_payment_profile_id: "tok_1EY..." }
    end
    let(:stored_card_attrs) do
      {
        user:,
        gateway_customer_profile_id: "cus_F2T...",
        gateway_payment_profile_id: "card_1EY..."
      }
    end
    let(:stored_default_card_attrs) do
      stored_card_attrs.merge(is_default: true)
    end

    context "when a card is already set as the default" do
      let!(:card1) { create(:credit_card, stored_default_card_attrs) }

      context "and I create a new card" do
        context "without specifying it as the default" do
          let!(:card2) { create(:credit_card, stored_card_attrs) }

          it "keeps the existing default" do
            expect(card1.reload.is_default).to be true
            expect(card2.reload.is_default).to be false
          end
        end

        context "and I specify it as the default" do
          let!(:card2) { create(:credit_card, stored_default_card_attrs) }

          it "switches the default to the new card" do
            expect(card1.reload.is_default).to be false
            expect(card2.reload.is_default).to be true
          end
        end
      end

      context "and I update another card" do
        let!(:card2) { create(:credit_card, user:) }

        context "without specifying it as the default" do
          it "keeps the existing default" do
            card2.update!(stored_card_attrs)

            expect(card1.reload.is_default).to be true
            expect(card2.reload.is_default).to be false
          end
        end

        context "and I specify it as the default" do
          it "switches the default to the updated card" do
            card2.update!(stored_default_card_attrs)

            expect(card1.reload.is_default).to be false
            expect(card2.reload.is_default).to be true
          end
        end
      end
    end

    context "when no card is currently set as the default for a user" do
      context "and I create a new card" do
        context "without specifying it as the default" do
          let!(:card1) { create(:credit_card, stored_card_attrs) }

          it "sets it as the default anyway" do
            expect(card1.reload.is_default).to be true
          end
        end

        context "and I specify it as the default" do
          let!(:card1) { create(:credit_card, stored_default_card_attrs) }

          it "sets it as the default" do
            expect(card1.reload.is_default).to be true
          end
        end
      end

      context "and the checkout creates a card" do
        let!(:card1) { create(:credit_card, onetime_card_attrs) }
        let(:store_card_profile_attrs) {
          {
            cc_type: "visa",
            gateway_customer_profile_id: "cus_FH9HflKAJw6Kxy",
            gateway_payment_profile_id: "card_1EmayNBZvgSKc1B2wctIzzoh"
          }
        }

        it "doesn't set a one-time card as the default" do
          expect(card1.reload.is_default).to be false
        end

        it "sets a re-usable card as the default" do
          # The checkout first creates a one-time card and then converts it
          # to a re-usable card.
          # This imitates Stripe::ProfileStorer.
          card1.update!(store_card_profile_attrs)
          expect(card1.reload.is_default).to be true
        end
      end
    end
  end
end
