# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Spree::CustomerReturn, type: :model do
  before do
    allow_any_instance_of(Spree::Order).to receive_messages(return!: true)
  end

  describe ".validation" do
    describe "#return_items_belong_to_same_order" do
      let(:customer_return) { build(:customer_return) }

      let(:first_order) { create(:order_with_line_items) }
      let(:second_order) { first_order }

      let(:first_shipment) { first_order.shipments.first }
      let(:second_shipment) { second_order.shipments.first }

      let(:first_inventory_unit)  { build(:inventory_unit, shipment: first_shipment) }
      let(:first_return_item)     { build(:return_item, inventory_unit: first_inventory_unit) }

      let(:second_inventory_unit) { build(:inventory_unit, shipment: second_shipment) }
      let(:second_return_item)    { build(:return_item, inventory_unit: second_inventory_unit) }

      subject { customer_return.valid? }

      before do
        customer_return.return_items << first_return_item
        customer_return.return_items << second_return_item
      end

      context "return items are part of different orders" do
        let(:second_order) { create(:order_with_line_items) }

        it "is not valid" do
          expect(subject).to eq false
        end

        it "adds an error message" do
          subject
          expect(customer_return.errors.full_messages).to include(I18n.t('spree.return_items_cannot_be_associated_with_multiple_orders'))
        end
      end

      context "return items are part of the same order" do
        let(:second_order) { first_order }

        it "is valid" do
          expect(subject).to eq true
        end
      end

      context "inventory is not present" do
        before do
          customer_return.return_items.clear

          customer_return.return_items << Spree::ReturnItem.new
        end

        it "is invalid" do
          expect(subject).to eq false
        end
      end
    end
  end

  describe ".before_create" do
    describe "#generate_number" do
      context "number is assigned" do
        let(:customer_return) { Spree::CustomerReturn.new(number: '123') }

        it "should return the assigned number" do
          customer_return.save
          expect(customer_return.number).to eq('123')
        end
      end

      context "number is not assigned" do
        let(:customer_return) { Spree::CustomerReturn.new(number: nil) }

        before do
          allow(customer_return).to receive_messages(valid?: true, process_return!: true)
        end

        it "should assign number with random CR number" do
          customer_return.save
          expect(customer_return.number).to match(/CR\d{9}/)
        end
      end
    end
  end

  describe "#total" do
    let(:amount) { 15.0 }
    let(:tax_amount) { 5.0 }
    let(:customer_return) { create(:customer_return, line_items_count: 2) }

    before do
      Spree::ReturnItem.where(customer_return_id: customer_return.id).update_all(amount:, additional_tax_total: tax_amount)
      customer_return.reload
    end

    subject { customer_return.total }

    it "returns the sum of the return item's total amount" do
      expect(subject).to eq((amount * 2) + (tax_amount * 2))
    end
  end

  describe "#display_total" do
    let(:customer_return) { stub_model(Spree::CustomerReturn, total: 21.22, currency: "GBP") }

    it "returns a Spree::Money" do
      expect(customer_return.display_total).to eq(Spree::Money.new(21.22, currency: "GBP"))
    end
  end

  describe "#currency" do
    let(:order) { stub_model(Spree::Order, currency: "GBP") }
    let(:customer_return) { stub_model(Spree::CustomerReturn, order:) }

    it 'returns the order currency' do
      expect(Spree::Config.currency).to eq("USD")
      expect(customer_return.currency).to eq("GBP")
    end
  end

  describe "#amount" do
    let(:amount) { 15.0 }
    let(:customer_return) { create(:customer_return, line_items_count: 2) }

    before do
      Spree::ReturnItem.where(customer_return_id: customer_return.id).update_all(amount:)
    end

    subject { customer_return.amount }

    it "returns the sum of the return item's amount" do
      expect(subject).to eq(amount * 2)
    end
  end

  describe "#display_amount" do
    let(:customer_return) { stub_model(Spree::CustomerReturn, amount: 21.22, currency: "RUB") }

    it "returns a Spree::Money" do
      expect(customer_return.display_amount).to eq(Spree::Money.new(21.22, currency: "RUB"))
    end
  end

  describe "#order" do
    let(:return_item) { create(:return_item) }
    let(:customer_return) { build(:customer_return, return_items: [return_item]) }

    subject { customer_return.order }

    it "returns the order associated with the return item's inventory unit" do
      expect(subject).to eq return_item.inventory_unit.order
    end
  end

  describe "#order_id" do
    subject { customer_return.order_id }

    context "return item is not associated yet" do
      let(:customer_return) { build(:customer_return) }

      it "is nil" do
        expect(subject).to be_nil
      end
    end

    context "has an associated return item" do
      let(:return_item) { create(:return_item) }
      let(:customer_return) { build(:customer_return, return_items: [return_item]) }

      it "is the return item's inventory unit's order id" do
        expect(subject).to eq return_item.inventory_unit.order.id
      end
    end
  end

  context ".after_save" do
    let(:inventory_unit)  { create(:inventory_unit, state: 'shipped', order: create(:shipped_order)) }
    let(:return_item)     { build(:return_item, inventory_unit:) }

    context "to the initial stock location" do
      it "should mark the received inventory units are returned" do
        create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: inventory_unit.shipment.stock_location_id)
        return_item.receive!
        expect(inventory_unit.reload.state).to eq 'returned'
      end

      it "should update the stock item counts in the stock location" do
        expect do
          create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: inventory_unit.shipment.stock_location_id)
          return_item.receive!
        end.to change { inventory_unit.find_stock_item.count_on_hand }.by(1)
      end

      context 'with Config.track_inventory_levels == false' do
        before do
          stub_spree_preferences(track_inventory_levels: false)
          expect(Spree::StockItem).not_to receive(:find_by)
          expect(Spree::StockMovement).not_to receive(:create!)
        end

        it "should NOT update the stock item counts in the stock location" do
          count_on_hand = inventory_unit.find_stock_item.count_on_hand
          create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: inventory_unit.shipment.stock_location_id)
          expect(inventory_unit.find_stock_item.count_on_hand).to eql count_on_hand
        end
      end
    end

    context "to a different stock location" do
      let(:new_stock_location) { create(:stock_location, name: "other") }

      it "should update the stock item counts in new stock location" do
        expect {
          create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: new_stock_location.id)
          return_item.receive!
        }.to change {
          Spree::StockItem.where(variant_id: inventory_unit.variant_id, stock_location_id: new_stock_location.id).first.count_on_hand
        }.by(1)
      end

      it "should NOT raise an error when no stock item exists in the stock location" do
        inventory_unit.find_stock_item.destroy
        create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: new_stock_location.id)
      end

      it "should NOT raise an error when a soft-deleted stock item exists in the stock location" do
        inventory_unit.find_stock_item.discard
        create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: new_stock_location.id)
      end

      it "should not update the stock item counts in the original stock location" do
        count_on_hand = inventory_unit.find_stock_item.count_on_hand
        create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: new_stock_location.id)
        expect(inventory_unit.find_stock_item.count_on_hand).to eq(count_on_hand)
      end
    end

    context "it was not received" do
      before do
        return_item.update!(reception_status: "lost_in_transit")
      end

      it 'should not updated inventory unit to returned' do
        create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: inventory_unit.shipment.stock_location_id)
        expect(inventory_unit.reload.state).to eq 'shipped'
      end

      it "should not update the stock item counts in the stock location" do
        expect do
          create(:customer_return_without_return_items, return_items: [return_item], stock_location_id: inventory_unit.shipment.stock_location_id)
        end.to_not change { inventory_unit.find_stock_item.count_on_hand }
      end
    end
  end

  describe '#fully_reimbursed?' do
    let(:customer_return) { create(:customer_return) }

    let!(:default_refund_reason) { Spree::RefundReason.find_or_create_by!(name: Spree::RefundReason::RETURN_PROCESSING_REASON, mutable: false) }

    subject { customer_return.fully_reimbursed? }

    context 'when some return items are undecided' do
      it { is_expected.to be false }
    end

    context 'when all return items are decided' do
      context 'when all return items are rejected' do
        before { customer_return.return_items.each(&:reject!) }

        it { is_expected.to be true }
      end

      context 'when all return items are accepted' do
        before { customer_return.return_items.each(&:accept!) }

        context 'when some return items have no reimbursement' do
          it { is_expected.to be false }
        end

        context 'when all return items have a reimbursement' do
          let!(:reimbursement) { create(:reimbursement, customer_return:) }

          context 'when some reimbursements are not reimbursed' do
            it { is_expected.to be false }
          end

          context 'when all reimbursements are reimbursed' do
            let(:created_by_user) { create(:user, email: 'user@email.com') }
            before { reimbursement.perform!(created_by: created_by_user) }

            it { is_expected.to be true }
          end
        end
      end
    end
  end
end
