module Billing
  module Stripe
    class RecordPayment
      def self.call(stripe_invoice:, club: nil)
        new(stripe_invoice: stripe_invoice, club: club).call
      end

      def initialize(stripe_invoice:, club: nil)
        @stripe_invoice = stripe_invoice
        @club = club
      end

      def call
        @club ||= resolve_club
        return unless @club

        @stripe_invoice = ensure_payments_expanded(@stripe_invoice)

        payment = Billing::Payment.find_or_initialize_by(stripe_invoice_id: @stripe_invoice.id)
        payment.assign_attributes(
          club: @club,
          stripe_payment_intent_id: payment_intent_id,
          stripe_charge_id: charge_id,
          amount_cents: @stripe_invoice.amount_paid,
          currency: @stripe_invoice.currency,
          status: map_status(@stripe_invoice.status),
          plan_slug: plan_slug,
          description: PaymentDescription.for_stripe_invoice(@stripe_invoice),
          paid_at: paid_at,
          invoice_pdf_url: @stripe_invoice.invoice_pdf,
          receipt_url: @stripe_invoice.hosted_invoice_url
        )
        payment.save!
        payment
      end

      private

      def resolve_club
        club_id = metadata_club_id(@stripe_invoice)
        return Club.find_by(id: club_id) if club_id.present?

        sub_id = InvoiceMetadata.subscription_id(@stripe_invoice)
        return unless sub_id.present?

        Club.joins(:subscription).find_by(subscriptions: { stripe_subscription_id: sub_id })
      end

      def metadata_club_id(obj)
        meta = obj.metadata
        return nil if meta.blank?

        meta["club_id"] || meta[:club_id]
      end

      def plan_slug
        meta = InvoiceMetadata.merged(@stripe_invoice)
        slug = meta["plan_slug"]
        slug = "premium_full" if slug == "premium"
        slug.presence_in(%w[premium_light premium_full])
      end

      def payment_intent_id
        InvoicePaymentRefs.payment_intent_id(@stripe_invoice)
      end

      def charge_id
        InvoicePaymentRefs.charge_id(@stripe_invoice)
      end

      def ensure_payments_expanded(invoice)
        return invoice if InvoicePaymentRefs.payment_entries(invoice).any?

        ::Stripe::Invoice.retrieve(id: invoice.id, expand: InvoicePaymentRefs::EXPAND)
      rescue ::Stripe::StripeError
        invoice
      end

      def paid_at
        ts = @stripe_invoice.status_transitions&.paid_at
        ts ? Time.zone.at(ts) : Time.current
      end

      def map_status(stripe_status)
        case stripe_status
        when "paid" then "paid"
        when "uncollectible", "void" then "failed"
        else "failed"
        end
      end
    end
  end
end
