class Enterprise::Billing::TopupCheckoutService
  include BillingHelper

  class Error < StandardError; end

  TOPUP_OPTIONS_CONFIG = 'CAPTAIN_TOPUP_OPTIONS'.freeze

  pattr_initialize [:account!]

  # Topup packages for the account's billing currency (used by the controller).
  def available_options
    topup_options
  end

  def create_checkout_session(credits:)
    topup_option = validate_and_find_topup_option(credits)
    charge_customer(topup_option, credits)
    fulfill_credits(credits, topup_option)

    {
      credits: credits,
      amount: topup_option[:amount],
      currency: topup_option[:currency]
    }
  end

  private

  def validate_and_find_topup_option(credits)
    raise Error, I18n.t('errors.topup.invalid_credits') unless credits.to_i.positive?
    raise Error, I18n.t('errors.topup.plan_not_eligible') if default_plan?(account)
    raise Error, I18n.t('errors.topup.stripe_customer_not_configured') if stripe_customer_id.blank?

    topup_option = find_topup_option(credits)
    raise Error, I18n.t('errors.topup.invalid_option') unless topup_option

    # Validate payment method exists
    validate_payment_method!

    topup_option
  end

  def validate_payment_method!
    customer = Stripe::Customer.retrieve(stripe_customer_id)

    return if customer.invoice_settings.default_payment_method.present? || customer.default_source.present?

    # Auto-set first payment method as default if available
    payment_methods = Stripe::PaymentMethod.list(customer: stripe_customer_id, limit: 1)
    raise Error, I18n.t('errors.topup.no_payment_method') if payment_methods.data.empty?

    Stripe::Customer.update(stripe_customer_id, invoice_settings: { default_payment_method: payment_methods.data.first.id })
  end

  def charge_customer(topup_option, credits)
    amount_cents = (topup_option[:amount] * 100).to_i
    currency = topup_option[:currency]
    description = "AI Credits Topup: #{credits} credits"

    invoice = Stripe::Invoice.create(
      customer: stripe_customer_id,
      currency: currency,
      collection_method: 'charge_automatically',
      auto_advance: false,
      description: description
    )

    Stripe::InvoiceItem.create(
      customer: stripe_customer_id,
      amount: amount_cents,
      currency: currency,
      invoice: invoice.id,
      description: description
    )

    finalize_and_pay(invoice.id)
  end

  def finalize_and_pay(invoice_id)
    Stripe::Invoice.finalize_invoice(invoice_id, { auto_advance: false })
    invoice = Stripe::Invoice.retrieve(invoice_id)
    return if invoice.status == 'paid'

    Stripe::Invoice.pay(invoice_id)
  rescue Stripe::CardError
    Stripe::Invoice.void_invoice(invoice_id)
    raise
  end

  def fulfill_credits(credits, topup_option)
    Enterprise::Billing::TopupFulfillmentService.new(account: account).fulfill(
      credits: credits,
      amount_cents: (topup_option[:amount] * 100).to_i,
      currency: topup_option[:currency]
    )
  end

  def stripe_customer_id
    account.custom_attributes['stripe_customer_id']
  end

  def find_topup_option(credits)
    topup_options.find { |opt| opt[:credits] == credits.to_i }
  end

  def topup_options
    # Label rows with the currency they were configured under, so a DEFAULT fallback can't relabel USD amounts and undercharge.
    options = configured_options
    currency = options[account.billing_currency].present? ? account.billing_currency : Enterprise::Billing::Currencies::DEFAULT
    rows = options[currency].presence || []
    rows.map { |opt| { credits: opt['credits'].to_i, amount: opt['amount'].to_f, currency: currency } }
  end

  def configured_options
    config = InstallationConfig.find_by(name: TOPUP_OPTIONS_CONFIG)&.value
    config = JSON.parse(config) if config.is_a?(String)
    config || {}
  end
end
