require 'spec_helper'

RSpec.describe SolidusBraintree::Source, type: :model do
  include_context 'when order is ready for payment'

  it 'is invalid without a payment_type set' do
    expect(described_class.new).to be_invalid
  end

  it 'is invalid with payment_type set to unknown type' do
    expect(described_class.new(payment_type: 'AndroidPay')).to be_invalid
  end

  describe 'attributes' do
    context 'with paypal_funding_source' do
      subject { build(:solidus_braintree_source, :paypal) }

      it 'can be nil' do
        subject.paypal_funding_source = nil

        expect(subject).to be_valid
      end

      it 'makes empty strings nil' do
        subject.paypal_funding_source = ''

        result = subject.save

        expect(result).to be(true)
        expect(subject.paypal_funding_source).to be_nil
      end

      it 'gets correctly mapped as an enum' do
        subject.paypal_funding_source = 'applepay'

        result = subject.save

        expect(result).to be(true)
        expect(subject.paypal_funding_source).to eq('applepay')
        expect(subject.applepay_funding?).to be(true)
      end

      it "doesn't become nil when the payment_type is a PAYPAL" do
        subject.payment_type = described_class::PAYPAL
        subject.paypal_funding_source = 'venmo'

        result = subject.save

        expect(result).to be(true)
        expect(subject.venmo_funding?).to be(true)
      end

      it 'becomes nil when the payment_type is a CREDIT CARD' do
        subject.payment_type = described_class::CREDIT_CARD
        subject.paypal_funding_source = 'venmo'

        result = subject.save

        expect(result).to be(true)
        expect(subject.paypal_funding_source).to be_nil
      end

      it 'becomes nil when the payment_type is APPLE PAY' do
        subject.payment_type = described_class::APPLE_PAY
        subject.paypal_funding_source = 'venmo'

        result = subject.save

        expect(result).to be(true)
        expect(subject.paypal_funding_source).to be_nil
      end
    end
  end

  describe '#payment_method' do
    it 'uses spree_payment_method' do
      expect(described_class.new.build_payment_method).to be_a Spree::PaymentMethod
    end
  end

  describe '#imported' do
    it 'is always false' do
      expect(described_class.new.imported).not_to be_truthy
    end
  end

  describe "#actions" do
    it "supports capture, void, and credit" do
      expect(described_class.new.actions).to eq %w[capture void credit]
    end
  end

  describe "#can_capture?" do
    subject { described_class.new.can_capture?(payment) }

    context "when the payment state is pending" do
      let(:payment) { build(:payment, state: "pending") }

      it { is_expected.to be_truthy }
    end

    context "when the payment state is checkout" do
      let(:payment) { build(:payment, state: "checkout") }

      it { is_expected.to be_truthy }
    end

    context "when the payment is completed" do
      let(:payment) { build(:payment, state: "completed") }

      it { is_expected.not_to be_truthy }
    end
  end

  describe '#can_void?' do
    subject { payment_source.can_void?(payment) }

    let(:payment_source) { described_class.new }
    let(:payment) { build(:payment) }

    let(:transaction_response) do
      double(:response, status: Braintree::Transaction::Status::SubmittedForSettlement)
    end

    let(:transaction_request) do
      double(:request, find: transaction_response)
    end

    before do
      allow(payment_source).to receive(:braintree_client) do
        double(:transaction, transaction: transaction_request)
      end
    end

    context 'when transaction id is not present' do
      let(:payment) { build(:payment, response_code: nil) }

      it { is_expected.to be(false) }
    end

    context 'when transaction has voidable status' do
      it { is_expected.to be(true) }
    end

    context 'when transaction has non voidable status' do
      let(:transaction_response) do
        double(:response, status: Braintree::Transaction::Status::Settled)
      end

      it { is_expected.to be(false) }
    end

    context 'when transaction is not found at Braintreee' do
      before do
        allow(transaction_request).to \
          receive(:find).and_raise(Braintree::NotFoundError)
      end

      it { is_expected.to be(false) }
    end
  end

  describe "#can_credit?" do
    subject { described_class.new.can_credit?(payment) }

    context "when the payment is completed" do
      context "when the credit allowed is 100" do
        let(:payment) { build(:payment, state: "completed", amount: 100) }

        it { is_expected.to be_truthy }
      end

      context "when the credit allowed is 0" do
        let(:payment) { build(:payment, state: "completed", amount: 0) }

        it { is_expected.not_to be_truthy }
      end
    end

    context "when the payment has not been completed" do
      let(:payment) { build(:payment, state: "checkout") }

      it { is_expected.not_to be_truthy }
    end
  end

  describe "#friendly_payment_type" do
    subject { described_class.new(payment_type: type).friendly_payment_type }

    context "when then payment type is PayPal" do
      let(:type) { "PayPalAccount" }

      it "returns the translated payment type" do
        expect(subject).to eq "PayPal"
      end
    end

    context "when the payment type is Apple Pay" do
      let(:type) { "ApplePayCard" }

      it "returns the translated payment type" do
        expect(subject).to eq "Apple Pay"
      end
    end

    context "when the payment type is Credit Card" do
      let(:type) { "CreditCard" }

      it "returns the translated payment type" do
        expect(subject).to eq "Credit Card"
      end
    end
  end

  describe "#apple_pay?" do
    subject { described_class.new(payment_type: type).apple_pay? }

    context "when the payment type is Apple Pay" do
      let(:type) { "ApplePayCard" }

      it { is_expected.to be true }
    end

    context "when the payment type is not Apple Pay" do
      let(:type) { "DogeCoin" }

      it { is_expected.to be false }
    end
  end

  describe "#paypal?" do
    subject { described_class.new(payment_type: type).paypal? }

    context "when the payment type is PayPal" do
      let(:type) { "PayPalAccount" }

      it { is_expected.to be true }
    end

    context "when the payment type is not PayPal" do
      let(:type) { "MonopolyMoney" }

      it { is_expected.to be false }
    end
  end

  describe "#credit_card?" do
    subject { described_class.new(payment_type: type).credit_card? }

    context "when the payment type is CreditCard" do
      let(:type) { "CreditCard" }

      it { is_expected.to be true }
    end

    context "when the payment type is not CreditCard" do
      let(:type) { "MonopolyMoney" }

      it { is_expected.to be false }
    end
  end

  describe "#venmo?" do
    subject { described_class.new(payment_type: type).venmo? }

    context "when the payment type is VenmoAccount" do
      let(:type) { "VenmoAccount" }

      it { is_expected.to be true }
    end

    context "when the payment type is not VenmoAccount" do
      let(:type) { "Swish" }

      it { is_expected.to be false }
    end
  end

  shared_context 'with unknown source token' do
    let(:braintree_payment_method) { double }

    before do
      allow(braintree_payment_method).to receive(:find) do
        raise Braintree::NotFoundError
      end
      allow(payment_source).to receive(:braintree_client) do
        instance_double(payment_method: braintree_payment_method)
      end
    end
  end

  shared_context 'with nil source token' do
    let(:braintree_payment_method) { double }

    before do
      allow(braintree_payment_method).to receive(:find) do
        raise ArgumentError
      end
      allow(payment_source).to receive(:braintree_client) do
        instance_double(payment_method: braintree_payment_method)
      end
    end
  end

  describe "#last_4" do
    subject { payment_source.last_4 }

    let(:method) { new_gateway.tap(&:save!) }
    let(:payment_source) { described_class.create!(payment_type: "CreditCard", payment_method: method) }
    let(:braintree_client) { method.braintree }

    context 'when token is known at braintree', vcr: {
      cassette_name: "source/last4",
      match_requests_on: [:braintree_uri]
    } do
      before do
        customer = braintree_client.customer.create

        method = braintree_client.payment_method.create({
          payment_method_nonce: "fake-valid-country-of-issuance-usa-nonce",
          customer_id: customer.customer.id
        })

        payment_source.update!(token: method.payment_method.token)
      end

      it "delegates to the braintree payment method" do
        method = braintree_client.payment_method.find(payment_source.token)
        expect(subject).to eql(method.last_4)
      end
    end

    context 'when the source token is not known at Braintree' do
      include_context 'with unknown source token'

      it { is_expected.to be_nil }
    end

    context 'when the source token is nil' do
      include_context 'with nil source token'

      it { is_expected.to be_nil }
    end
  end

  describe "#display_number" do
    subject { payment_source.display_number }

    let(:type) { nil }
    let(:payment_source) { described_class.new(payment_type: type) }

    context "when last_digits is a number" do
      before do
        allow(payment_source).to receive(:last_digits).and_return('1234')
      end

      it { is_expected.to eq 'XXXX-XXXX-XXXX-1234' }
    end

    context "when last_digits is nil" do
      before do
        allow(payment_source).to receive(:last_digits).and_return(nil)
      end

      it { is_expected.to eq 'XXXX-XXXX-XXXX-XXXX' }
    end

    context "when is a PayPal source" do
      let(:type) { "PayPalAccount" }

      before do
        allow(payment_source).to receive(:email).and_return('user@example.com')
      end

      it { is_expected.to eq 'user@example.com' }
    end

    context "when is a Venmo source" do
      let(:type) { "VenmoAccount" }

      before do
        allow(payment_source).to receive(:username).and_return('venmojoe')
      end

      it { is_expected.to eq('venmojoe') }
    end
  end

  describe "#card_type" do
    subject { payment_source.card_type }

    let(:method) { new_gateway.tap(&:save!) }
    let(:payment_source) { described_class.create!(payment_type: "CreditCard", payment_method: method) }
    let(:braintree_client) { method.braintree }

    context "when the token is known at braintree", vcr: {
      cassette_name: "source/card_type",
      match_requests_on: [:braintree_uri]
    } do
      before do
        customer = braintree_client.customer.create

        method = braintree_client.payment_method.create({
          payment_method_nonce: "fake-valid-country-of-issuance-usa-nonce", customer_id: customer.customer.id
        })

        payment_source.update!(token: method.payment_method.token)
      end

      it "delegates to the braintree payment method" do
        method = braintree_client.payment_method.find(payment_source.token)
        expect(subject).to eql(method.card_type)
      end
    end

    context 'when the source token is not known at Braintree' do
      include_context 'with unknown source token'

      it { is_expected.to be_nil }
    end

    context 'when the source token is nil' do
      include_context 'with nil source token'

      it { is_expected.to be_nil }
    end
  end

  describe '#display_paypal_funding_source' do
    let(:payment_source) { described_class.new }

    context 'when the EN locale exists' do
      it 'translates the funding source' do
        payment_source.paypal_funding_source = 'card'

        result = payment_source.display_paypal_funding_source

        expect(result).to eq('Credit or debit card')
      end
    end

    context "when the locale doesn't exist" do
      it 'returns the paypal_funding_source as the default' do
        allow(payment_source).to receive(:paypal_funding_source).and_return('non-existent')

        result = payment_source.display_paypal_funding_source

        expect(result).to eq('non-existent')
      end
    end
  end

  describe "#bin" do
    subject { payment_source.bin }

    let(:method) { new_gateway.tap(&:save!) }
    let(:payment_source) { described_class.create!(payment_type: "CreditCard", payment_method: method) }
    let(:braintree_client) { method.braintree }

    context "when the token is known at braintree", vcr: {
      cassette_name: "source/bin",
      match_requests_on: [:braintree_uri]
    } do
      before do
        customer = braintree_client.customer.create

        method = braintree_client.payment_method.create({
          payment_method_nonce: "fake-valid-country-of-issuance-usa-nonce", customer_id: customer.customer.id
        })

        payment_source.update!(token: method.payment_method.token)
      end

      it "delegates to the braintree payment method" do
        method = braintree_client.payment_method.find(payment_source.token)
        expect(subject).to eql(method.bin)
      end
    end

    context 'when the source token is not known at Braintree' do
      include_context 'with unknown source token'

      it { is_expected.to be_nil }
    end

    context 'when the source token is nil' do
      include_context 'with nil source token'

      it { is_expected.to be_nil }
    end
  end

  describe '#display_payment_type' do
    subject { described_class.new(payment_type: type).display_payment_type }

    context 'when type is CreditCard' do
      let(:type) { 'CreditCard' }

      it 'returns "Payment Type: Credit Card' do
        expect(subject).to eq('Payment Type: Credit Card')
      end
    end

    context 'when type is PayPalAccount' do
      let(:type) { 'PayPalAccount' }

      it 'returns "Payment Type: PayPal' do
        expect(subject).to eq('Payment Type: PayPal')
      end
    end

    context 'when type is VenmoAccount' do
      let(:type) { 'VenmoAccount' }

      it 'returns "Payment Type: Venmo' do
        expect(subject).to eq('Payment Type: Venmo')
      end
    end
  end

  describe '#reusable?' do
    subject { payment_source.reusable? }

    let(:payment_source) { described_class.new(token: token, nonce: nonce) }
    let(:nonce) { 'nonce67890' }

    context 'when source token is present' do
      let(:token) { 'token12345' }

      it { is_expected.to be_truthy }
    end

    context 'when source token is nil' do
      let(:token) { nil }

      it { is_expected.to be_falsy }
    end
  end
end