# frozen_string_literal: true

require "spec_helper"

RSpec.describe Spree::Admin::PaymentsController do
  let(:user) { order.user }
  let(:order) { create(:completed_order_with_fees) }

  before do
    sign_in create(:admin_user)
  end

  describe "POST /admin/orders/:order_number/payments.json" do
    let(:params) do
      {
        payment: {
          payment_method_id: payment_method.id, amount: order.total
        }
      }
    end
    let(:payment_method) do
      create(:payment_method, distributors: [order.distributor])
    end

    it "creates a payment" do
      expect {
        post("/admin/orders/#{order.number}/payments.json", params:)
      }.to change { order.payments.count }.by(1)
    end

    it "redirect to payments page" do
      post("/admin/orders/#{order.number}/payments.json", params:)

      expect(response).to redirect_to(spree.admin_order_payments_path(order))
      expect(flash[:success]).to eq "Payment has been successfully created!"
    end

    context "when failing to create payment" do
      it "redirects to payments page" do
        payment_mock = instance_double(Spree::Payment)
        allow(order.payments).to receive(:build).and_return(payment_mock)
        allow(payment_mock).to receive(:save).and_return(false)

        post("/admin/orders/#{order.number}/payments.json", params:)

        expect(response).to redirect_to(spree.admin_order_payments_path(order))
      end
    end

    context "when a gateway error happens" do
      let(:payment_method) do
        create(:stripe_sca_payment_method, distributors: [order.distributor])
      end

      it "redirect to payments page" do
        allow(Spree::Order).to receive(:find_by!).and_return(order)

        stripe_sca_payment_authorize =
          instance_double(OrderManagement::Order::StripeScaPaymentAuthorize)
        allow(OrderManagement::Order::StripeScaPaymentAuthorize).to receive(:new)
          .and_return(stripe_sca_payment_authorize)
        # Simulate an error
        allow(stripe_sca_payment_authorize).to receive(:call!) do
          order.errors.add(:base, "authorization_failure")
        end

        post("/admin/orders/#{order.number}/payments.json", params:)

        expect(response).to redirect_to(spree.admin_order_payments_path(order))
        expect(flash[:error]).to eq("Authorization Failure")
      end
    end

    context "with a VINE voucher", feature: :connected_apps do
      let(:vine_voucher) {
        create(:vine_voucher, code: 'some_code', enterprise: order.distributor, amount: 6)
      }
      let(:vine_voucher_redeemer) { instance_double(Vine::VoucherRedeemerService) }

      before do
        add_voucher_to_order(vine_voucher, order)

        allow(Vine::VoucherRedeemerService).to receive(:new).and_return(vine_voucher_redeemer)
      end

      it "completes the order and redirects to payment page" do
        expect(vine_voucher_redeemer).to receive(:redeem).and_return(true)

        post("/admin/orders/#{order.number}/payments.json", params:)

        expect(response).to redirect_to(spree.admin_order_payments_path(order))
        expect(flash[:success]).to eq "Payment has been successfully created!"

        expect(order.reload.state).to eq "complete"
      end

      context "when redeeming the voucher fails" do
        it "redirect to payments page" do
          allow(vine_voucher_redeemer).to receive(:redeem).and_return(false)
          allow(vine_voucher_redeemer).to receive(:errors).and_return(
            { redeeming_failed: "Redeeming the voucher failed" }
          )

          post("/admin/orders/#{order.number}/payments.json", params:)

          expect(response).to redirect_to(spree.admin_order_payments_path(order))
          expect(flash[:error]).to match "Redeeming the voucher failed"
        end
      end

      context "when an other error happens" do
        it "redirect to payments page" do
          allow(vine_voucher_redeemer).to receive(:redeem).and_return(false)
          allow(vine_voucher_redeemer).to receive(:errors).and_return(
            { vine_api: "There was an error communicating with the API" }
          )

          post("/admin/orders/#{order.number}/payments.json", params:)

          expect(response).to redirect_to(spree.admin_order_payments_path(order))
          expect(flash[:error]).to match "There was an error while trying to redeem your voucher"
        end
      end
    end
  end

  describe "PUT /admin/orders/:order_number/payments/:id/fire" do
    let(:payment) do
      create(
        :payment,
        payment_method: stripe_payment_method,
        order:,
        response_code: "pi_123",
        amount: order.total,
        state: "completed"
      )
    end
    let(:stripe_payment_method) do
      create(:stripe_sca_payment_method, distributors: [order.distributor])
    end
    let(:headers) { { HTTP_REFERER: spree.admin_order_payments_url(order) } }

    before do
      order.update(payments: [])
      order.payments << payment
    end

    context "with no event parameter" do
      it "redirect to payments page" do
        put(
          "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/fire",
          params: {},
          headers:
        )

        expect(response).to redirect_to(spree.admin_order_payments_url(order))
      end
    end

    context "with no payment source" do
      it "redirect to payments page" do
        allow(payment).to receive(:payment_source).and_return(nil)

        put(
          "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/fire?e=void",
          params: {},
          headers:
        )

        expect(response).to redirect_to(spree.admin_order_payments_url(order))
      end
    end

    context "with 'void' event" do
      before do
        allow(Spree::Payment).to receive(:find).and_return(payment)
      end

      it "calls void_transaction! on payment" do
        expect(payment).to receive(:void_transaction!)

        put(
          "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/fire?e=void",
          params: {},
          headers:
        )
      end

      it "redirect to payments page" do
        allow(payment).to receive(:void_transaction!).and_return(true)

        put(
          "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/fire?e=void",
          params: {},
          headers:
        )

        expect(response).to redirect_to(spree.admin_order_payments_url(order))
        expect(flash[:success]).to eq "Payment Updated"
      end

      context "when void_transaction! fails" do
        it "set an error flash message" do
          allow(payment).to receive(:void_transaction!).and_return(false)

          put(
            "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/fire?e=void",
            params: {},
            headers:
          )

          expect(response).to redirect_to(spree.admin_order_payments_url(order))
          expect(flash[:error]).to eq "Could not update the payment"
        end
      end
    end

    context "with 'capture_and_complete_order' event" do
      before do
        allow(Spree::Payment).to receive(:find).and_return(payment)
      end

      it "calls capture_and_complete_order! on payment" do
        expect(payment).to receive(:capture_and_complete_order!)

        put(
          "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/" \
          "fire?e=capture_and_complete_order",
          params: {},
          headers:
        )
      end

      it "redirect to payments page" do
        allow(payment).to receive(:capture_and_complete_order!).and_return(true)

        put(
          "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/" \
          "fire?e=capture_and_complete_order",
          params: {},
          headers:
        )

        expect(response).to redirect_to(spree.admin_order_payments_url(order))
        expect(flash[:success]).to eq "Payment Updated"
      end

      context "when capture_and_complete_order! fails" do
        it "set an error flash message" do
          allow(payment).to receive(:capture_and_complete_order!).and_return(false)

          put(
            "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/" \
            "fire?e=capture_and_complete_order",
            params: {},
            headers:
          )

          expect(response).to redirect_to(spree.admin_order_payments_url(order))
          expect(flash[:error]).to eq "Could not update the payment"
        end
      end

      context "with a VINE voucher", feature: :connected_apps do
        let(:vine_voucher) {
          create(:vine_voucher, code: 'some_code', enterprise: order.distributor, amount: 6)
        }
        let(:vine_voucher_redeemer) { instance_double(Vine::VoucherRedeemerService) }

        before do
          add_voucher_to_order(vine_voucher, order)

          allow(Vine::VoucherRedeemerService).to receive(:new).and_return(vine_voucher_redeemer)
        end

        it "completes the order and redirects to payment page" do
          expect(vine_voucher_redeemer).to receive(:redeem).and_return(true)

          put(
            "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/" \
            "fire?e=capture_and_complete_order",
            params: {},
            headers:
          )

          expect(response).to redirect_to(spree.admin_order_payments_url(order))
          expect(flash[:success]).to eq "Payment Updated"

          expect(order.reload.state).to eq "complete"
        end

        context "when redeeming the voucher fails" do
          it "redirect to payments page" do
            allow(vine_voucher_redeemer).to receive(:redeem).and_return(false)
            allow(vine_voucher_redeemer).to receive(:errors).and_return(
              { redeeming_failed: "Redeeming the voucher failed" }
            )

            put(
              "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/" \
              "fire?e=capture_and_complete_order",
              params: {},
              headers:
            )

            expect(response).to redirect_to(spree.admin_order_payments_url(order))
            expect(flash[:error]).to match "Redeeming the voucher failed"
          end
        end

        context "when an other error happens" do
          it "redirect to payments page" do
            allow(vine_voucher_redeemer).to receive(:redeem).and_return(false)
            allow(vine_voucher_redeemer).to receive(:errors).and_return(
              { vine_api: "There was an error communicating with the API" }
            )

            put(
              "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/" \
              "fire?e=capture_and_complete_order",
              params: {},
              headers:
            )

            expect(response).to redirect_to(spree.admin_order_payments_url(order))
            expect(flash[:error]).to match "There was an error while trying to redeem your voucher"
          end
        end
      end
    end

    context "when something unexpected happen" do
      before do
        allow(Spree::Payment).to receive(:find).and_return(payment)
        allow(payment).to receive(:void_transaction!).and_raise(StandardError, "Unexpected !")
      end

      it "log the error message" do
        # The redirect_do also calls Rails.logger.error
        expect(Rails.logger).to receive(:error).with("Unexpected !").ordered
        expect(Rails.logger).to receive(:error).with(/Redirected/).ordered
        expect(Bugsnag).to receive(:notify).with(StandardError)

        put(
          "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/fire?e=void",
          params: {},
          headers: { HTTP_REFERER: spree.admin_order_payments_url(order) }
        )

        expect(flash[:error]).to eq "Unexpected !"
      end

      it "redirect to payments page" do
        put(
          "/admin/orders/#{order.number}/payments/#{order.payments.first.id}/fire?e=void",
          params: {},
          headers:
        )

        expect(response).to redirect_to(spree.admin_order_payments_url(order))
      end
    end
  end

  def add_voucher_to_order(voucher, order)
    voucher.create_adjustment(voucher.code, order)
    OrderManagement::Order::Updater.new(order).update_voucher
  end
end
