require 'rails_helper'
require 'webmock/rspec'

RSpec.describe "Donations", type: :request do
  let(:user) { create :user, cn: 'jimmy', ou: 'kosmos.org' }

  before do
    Warden.test_mode!
    login_as user, scope: :user
  end

  after { Warden.test_reset! }

  describe "#create" do
    describe "with disabled methods" do
      before do
        Setting.btcpay_enabled = false
      end

      it "returns a 403" do
        post "/contributions/donations", params: { donation_method: "btcpay" }
        expect(response).to have_http_status(:forbidden)
      end
    end

    describe "with fake methods" do
      it "returns a 403" do
        post "/contributions/donations", params: { donation_method: "remotestorage" }
        expect(response).to have_http_status(:forbidden)
      end
    end

    describe "with invalid fiat currency" do
      it "returns a 422" do
        post "/contributions/donations", params: {
          donation_method: "btcpay", amount: "10", currency: "GBP"
        }
        expect(response).to have_http_status(:unprocessable_entity)
      end
    end

    describe "with bad amount" do
      it "returns a 422" do
        post "/contributions/donations", params: {
          donation_method: "btcpay", amount: ""
        }
        expect(response).to have_http_status(:unprocessable_entity)
      end
    end

    describe "with BTCPay" do
      before { Setting.btcpay_enabled = true }

      describe "amount in EUR" do
        before do
          expect(BtcpayManager::CreateInvoice).to receive(:call)
            .with(amount: 25, currency: "EUR", redirect_url: "http://www.example.com/contributions/donations/1/confirm_btcpay")
            .and_return({
              "id" => "Q9GBe143HJIkdpZeH4Ftx5",
              "amount" => "25",
              "currency" => "EUR",
              "checkoutLink" => "#{Setting.btcpay_api_url}/i/Q9GBe143HJIkdpZeH4Ftx5",
              "expirationTime" => 1707908626,
              "checkout" => { "redirectURL" => "http://www.example.com/contributions/donations/1/confirm_btcpay" }
            })

          post "/contributions/donations", params: {
            donation_method: "btcpay", amount: "25", currency: "EUR",
            public_name: "Mickey"
          }
        end

        it "creates a new donation record" do
          expect(user.donations.count).to eq(1)
          donation = user.donations.first
          expect(donation.donation_method).to eq("btcpay")
          expect(donation.payment_method).to be_nil
          expect(donation.paid_at).to be_nil
          expect(donation.public_name).to eq("Mickey")
          expect(donation.amount_sats).to be_nil
          expect(donation.fiat_amount).to eq(2500)
          expect(donation.fiat_currency).to eq("EUR")
          expect(donation.btcpay_invoice_id).to eq("Q9GBe143HJIkdpZeH4Ftx5")
        end

        it "redirects to the BTCPay checkout page" do
          expect(response).to redirect_to("https://btcpay.example.com/i/Q9GBe143HJIkdpZeH4Ftx5")
        end
      end

      describe "amount in sats" do
        before do
          expect(BtcpayManager::CreateInvoice).to receive(:call)
            .with(amount: 0.0001, currency: "BTC", redirect_url: "http://www.example.com/contributions/donations/1/confirm_btcpay")
            .and_return({
              "id" => "Q9GBe143HJIkdpZeH4Ftx5",
              "amount" => "0.0001",
              "currency" => "BTC",
              "checkoutLink" => "#{Setting.btcpay_api_url}/i/Q9GBe143HJIkdpZeH4Ftx5",
              "expirationTime" => 1707908626,
              "checkout" => { "redirectURL" => "http://www.example.com/contributions/donations/1/confirm_btcpay" }
            })

          post "/contributions/donations", params: {
            donation_method: "btcpay", amount: "10000", currency: "sats",
            public_name: "Garret Holmes"
          }
        end

        it "creates a new donation record" do
          expect(user.donations.count).to eq(1)
          donation = user.donations.first
          expect(donation.donation_method).to eq("btcpay")
          expect(donation.payment_method).to be_nil
          expect(donation.paid_at).to be_nil
          expect(donation.public_name).to eq("Garret Holmes")
          expect(donation.amount_sats).to eq(10000)
          expect(donation.fiat_amount).to be_nil
          expect(donation.fiat_currency).to be_nil
          expect(donation.btcpay_invoice_id).to eq("Q9GBe143HJIkdpZeH4Ftx5")
        end

        it "redirects to the BTCPay checkout page" do
          expect(response).to redirect_to("https://btcpay.example.com/i/Q9GBe143HJIkdpZeH4Ftx5")
        end
      end
    end
  end

  describe "#confirm_btcpay" do
    before { Setting.btcpay_enabled = true }

    describe "with donation of another user" do
      let(:other_user) { create :user, id: 3, cn: "carl", ou: 'kosmos.org', email: "carl@example.com" }

      before do
        @donation = other_user.donations.create!(
          donation_method: "btcpay", btcpay_invoice_id: "123abc",
          fiat_amount: 25, fiat_currency: "EUR", paid_at: nil
        )
        get confirm_btcpay_contributions_donation_path(@donation.id)
      end

      it "returns a 404" do
        expect(response).to have_http_status(:not_found)
      end
    end

    describe "with confirmed donation" do
      before do
        @donation = user.donations.create!(
          donation_method: "btcpay", btcpay_invoice_id: "123abc",
          fiat_amount: 25, fiat_currency: "EUR",
          paid_at: "2024-02-16", payment_status: "settled"
        )
        get confirm_btcpay_contributions_donation_path(@donation.id)
      end

      it "redirects to the donations index" do
        expect(response).to redirect_to(contributions_donations_url)
      end
    end

    describe "settled via Lightning" do
      describe "amount in EUR" do
        subject do
          user.donations.create!(
            donation_method: "btcpay", btcpay_invoice_id: "MCkDbf2cUgBuuisUCgnRnb",
            fiat_amount: 25, fiat_currency: "EUR", paid_at: nil
          )
        end

        before do
          invoice = File.read("#{Rails.root}/spec/fixtures/btcpay/lightning_eur_settled_invoice.json")
          payments = File.read("#{Rails.root}/spec/fixtures/btcpay/lightning_eur_settled_payments.json")
          stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/MCkDbf2cUgBuuisUCgnRnb")
            .to_return(status: 200, headers: {}, body: invoice)
          stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/MCkDbf2cUgBuuisUCgnRnb/payment-methods")
            .to_return(status: 200, headers: {}, body: payments)

          get confirm_btcpay_contributions_donation_path(subject)
        end

        it "updates the donation record" do
          subject.reload
          expect(subject.paid_at).not_to be_nil
          expect(subject.amount_sats).to eq(2061)
        end

        it "redirects to the donations index" do
          expect(response).to redirect_to(contributions_donations_url)
        end
      end

      describe "amount in sats" do
        subject do
          user.donations.create!(
            donation_method: "btcpay", btcpay_invoice_id: "JxjfeJi1TtX8FcWSjEvGxg",
            amount_sats: 10000, fiat_amount: nil, fiat_currency: nil, paid_at: nil
          )
        end

        before do
          invoice = File.read("#{Rails.root}/spec/fixtures/btcpay/lightning_sats_settled_invoice.json")
          payments = File.read("#{Rails.root}/spec/fixtures/btcpay/lightning_sats_settled_payments.json")
          stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/JxjfeJi1TtX8FcWSjEvGxg")
            .to_return(status: 200, headers: {}, body: invoice)
          stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/JxjfeJi1TtX8FcWSjEvGxg/payment-methods")
            .to_return(status: 200, headers: {}, body: payments)

          expect(BtcpayManager::FetchExchangeRate).to receive(:call)
            .with(fiat_currency: "EUR").and_return(48532.00)

          get confirm_btcpay_contributions_donation_path(subject)
        end

        it "updates the donation record" do
          subject.reload
          expect(subject.paid_at).not_to be_nil
          expect(subject.amount_sats).to eq(10000)
          expect(subject.fiat_amount).to eq(485)
          expect(subject.fiat_currency).to eq("EUR")
        end

        it "redirects to the donations index" do
          expect(response).to redirect_to(contributions_donations_url)
        end
      end
    end

    describe "on-chain" do
      describe "waiting for confirmations" do
        subject do
          user.donations.create!(
            donation_method: "btcpay", btcpay_invoice_id: "K4e31MhbLKmr3D7qoNYRd3",
            fiat_amount: 120, fiat_currency: "USD", paid_at: nil
          )
        end

        before do
          invoice = File.read("#{Rails.root}/spec/fixtures/btcpay/onchain_eur_processing_invoice.json")
          payments = File.read("#{Rails.root}/spec/fixtures/btcpay/onchain_eur_processing_payments.json")
          stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/K4e31MhbLKmr3D7qoNYRd3")
            .to_return(status: 200, headers: {}, body: invoice)
          stub_request(:get, "http://btcpay.example.com/api/v1/stores/123456/invoices/K4e31MhbLKmr3D7qoNYRd3/payment-methods")
            .to_return(status: 200, headers: {}, body: payments)

          get confirm_btcpay_contributions_donation_path(subject)
        end

        it "updates the donation record" do
          subject.reload
          expect(subject.paid_at).to be_nil
          expect(subject.amount_sats).to eq(191354)
          expect(subject.payment_status).to eq("processing")
        end

        it "enqueues a job to periodically check the invoice status" do
          expect(enqueued_jobs.size).to eq(1)
          expect(enqueued_jobs.first["job_class"]).to eq("BtcpayCheckDonationJob")
          expect(enqueued_jobs.first['arguments'][0]["_aj_globalid"]).to eq("gid://akkounts/Donation/#{subject.id}")
        end

        it "redirects to the donations index" do
          expect(response).to redirect_to(contributions_donations_url)
        end
      end
    end
  end
end
