require 'solidus_braintree_spec_helper'

RSpec.describe SolidusBraintree::TransactionImport do
  let(:order) { Spree::Order.new }
  let!(:country) { create :country, iso: "US" }
  let(:braintree_gateway) { SolidusBraintree::Gateway.new }
  let(:transaction_address) { nil }
  let(:transaction) do
    SolidusBraintree::Transaction.new(
      nonce: 'abcd1234',
      payment_type: "ApplePayCard",
      address: transaction_address,
      payment_method: braintree_gateway,
      email: "test@example.com",
      phone: "123-456-6789"
    )
  end
  let(:transaction_import) { described_class.new(order, transaction) }

  describe "#valid?" do
    subject { transaction_import.valid? }

    it { is_expected.to be true }

    context "with invalid transaction" do
      let(:transaction) { SolidusBraintree::Transaction.new }

      it { is_expected.to be false }
    end

    context "with invalid address" do
      let(:transaction_address) do
        SolidusBraintree::TransactionAddress.new(
          name: "Bruce Wayne",
          address_line_1: "42 Spruce Lane",
          city: "Gotham",
          state_code: "WA",
          country_code: "US"
        )
      end

      before do
        create(:state, state_code: "WA")
      end

      it { is_expected.to be false }

      it "sets useful error messages" do
        transaction_import.valid?
        expect(transaction_import.errors.full_messages).
          to eq ["Address is invalid", "Address Zip can't be blank"]
      end
    end
  end

  describe '#source' do
    subject { described_class.new(order, transaction).source }

    it { is_expected.to be_a SolidusBraintree::Source }

    it 'takes the nonce from the transaction' do
      expect(subject.nonce).to eq 'abcd1234'
    end

    it 'takes the payment type from the transaction' do
      expect(subject.payment_type).to eq 'ApplePayCard'
    end

    it 'takes the payment method from the transaction' do
      expect(subject.payment_method).to eq braintree_gateway
    end

    it 'takes the paypal funding source from the transaction' do
      transaction.paypal_funding_source = 'paypal'

      expect(subject.paypal_funding_source).to eq('paypal')
    end

    context 'when order has a user' do
      let(:user) { Spree.user_class.new }
      let(:order) { Spree::Order.new user: user }

      it 'associates user to the source' do
        expect(subject.user).to eq user
      end
    end
  end

  describe '#user' do
    subject { described_class.new(order, transaction).user }

    it { is_expected.to be_nil }

    context 'when order has a user' do
      let(:user) { Spree.user_class.new }
      let(:order) { Spree::Order.new user: user }

      it { is_expected.to eq user }
    end
  end

  describe '#import!' do
    subject { described_class.new(order, transaction).import!(end_state) }

    let(:store) { create :store }
    let(:variant) { create :variant }
    let(:line_item) { Spree::LineItem.new(variant: variant, quantity: 1, price: 10) }
    let(:address) { create :address, country: country }
    let(:order) {
      Spree::Order.create(
        number: "R999999999",
        store: store,
        line_items: [line_item],
        ship_address: address,
        currency: 'USD',
        total: 10,
        email: 'test@example.com'
      )
    }
    let(:payment_method) { create_gateway }

    let(:transaction_address) { nil }
    let(:end_state) { 'confirm' }

    let(:transaction) do
      SolidusBraintree::Transaction.new(
        nonce: 'fake-valid-nonce',
        payment_method: payment_method,
        address: transaction_address,
        payment_type: SolidusBraintree::Source::PAYPAL,
        phone: '123-456-7890',
        email: 'user@example.com'
      )
    end

    before do
      # create a shipping method so we can push through to the end
      create :shipping_method, cost: 5

      # ensure payments have the same number so VCR matches the request body
      allow_any_instance_of(Spree::Payment).
        to receive(:generate_identifier).
        and_return("ABCD1234")
    end

    context "with passing validation", vcr: {
      cassette_name: 'transaction/import/valid',
      match_requests_on: [:braintree_uri]
    } do
      context "when order end state is confirm" do
        it 'advances order to confirm state' do
          subject
          expect(order.state).to eq 'confirm'
        end

        it 'has a payment for the cost of line items + shipment' do
          subject
          expect(order.payments.first.amount).to eq 15
        end

        it 'is complete and capturable', aggregate_failures: true, vcr: {
          cassette_name: 'transaction/import/valid/capture',
          match_requests_on: [:braintree_uri]
        } do
          subject
          order.complete

          expect(order).to be_complete
          expect(order.payments.first).to be_pending

          order.payments.first.capture!
          # need to reload, as capture will update the order
          expect(order.reload).to be_paid
        end
      end

      context "when order end state is delivery" do
        let(:end_state) { 'delivery' }

        it "advances the order to delivery" do
          subject
          expect(order.state).to eq 'delivery'
        end

        it "has a payment for the cost of line items" do
          subject
          expect(order.payments.first.amount).to eq 10
        end
      end

      context 'when transaction has address' do
        let!(:new_york) { create :state, country: country, abbr: 'NY' }

        let(:transaction_address) do
          SolidusBraintree::TransactionAddress.new(
            country_code: 'US',
            name: 'Thaddeus Venture',
            city: 'New York',
            state_code: 'NY',
            address_line_1: '350 5th Ave',
            zip: '10118'
          )
        end

        it 'uses the new address', aggregate_failures: true do
          subject
          expect(order.shipping_address.address1).to eq '350 5th Ave'
          expect(order.shipping_address.country).to eq country
          expect(order.shipping_address.state).to eq new_york
        end

        context 'when transaction has paypal funding source' do
          it 'saves it to the payment source' do
            transaction.paypal_funding_source = 'paypal'

            subject

            source = SolidusBraintree::Source.last
            expect(source.paypal_funding_source).to eq('paypal')
          end
        end

        context 'with a tax category' do
          before do
            zone = Spree::Zone.create name: 'nyc tax'
            zone.members << Spree::ZoneMember.new(zoneable: new_york)
            create :tax_rate, zone: zone
          end

          it 'includes the tax in the payment' do
            subject
            expect(order.payments.first.amount).to eq 16
          end
        end

        context 'with a less expensive tax category' do
          before do
            original_zone = Spree::Zone.create name: 'first address tax'
            original_zone.members << Spree::ZoneMember.new(zoneable: address.state)
            original_tax_rate = create :tax_rate, zone: original_zone, amount: 0.2

            # new address is NY
            ny_zone = Spree::Zone.create name: 'nyc tax'
            ny_zone.members << Spree::ZoneMember.new(zoneable: new_york)
            create :tax_rate, tax_categories: [original_tax_rate.tax_categories.first], zone: ny_zone, amount: 0.1
          end

          it 'includes the lower tax in the payment' do
            # so shipments and shipment cost is calculated before transaction import
            order.next!; order.next!
            # precondition
            expect(order.additional_tax_total).to eq 2
            expect(order.total).to eq 17

            subject
            expect(order.additional_tax_total).to eq 1
            expect(order.payments.first.amount).to eq 16
          end
        end
      end
    end

    context "when validation fails" do
      let(:transaction_address) do
        SolidusBraintree::TransactionAddress.new(
          country_code: 'US',
          name: 'Thaddeus Venture',
          city: 'New York',
          state_code: 'NY',
          address_line_1: '350 5th Ave'
        )
      end

      it "raises an error with the validation messages" do
        expect { subject }.to raise_error(
          SolidusBraintree::TransactionImport::InvalidImportError
        )
      end
    end

    context "with checkout flow", vcr: {
      cassette_name: 'transaction/import/valid',
      match_requests_on: [:braintree_uri]
    } do
      it "is not restarted by default" do
        expect(order).not_to receive(:restart_checkout_flow)
        subject
      end

      context "with restart_checkout: true" do
        subject do
          described_class.new(order, transaction).import!(end_state, restart_checkout: true)
        end

        it "is restarted" do
          expect(order).to receive(:restart_checkout_flow)
          subject
        end
      end
    end
  end
end