module Billing
  module Stripe
    class ChangePlan
      VALID_PLANS = CheckoutSession::VALID_PLANS.freeze

      def self.call(club:, plan_slug:, interval: nil)
        new(club: club, plan_slug: plan_slug, interval: interval).call
      end

      def initialize(club:, plan_slug:, interval: nil)
        @club = club
        @plan_slug = plan_slug.to_s
        @interval = interval
        @sub = club.subscription
      end

      def call
        raise "Stripe non configurato" unless MatchLiveTv.stripe_enabled?
        raise "Piano non valido" unless VALID_PLANS.include?(@plan_slug)

        raise "Abbonamento omaggio: contatta il supporto per cambiare piano a pagamento" if @sub&.admin_comped?
        raise "Nessun abbonamento Stripe attivo" if @sub.blank? || @sub.stripe_subscription_id.blank?

        interval = resolve_interval(@sub)
        if @sub.plan.slug == @plan_slug && @sub.billing_interval == interval && !@sub.pending_plan_id?
          raise "Sei già su questo piano e intervallo di fatturazione"
        end

        kind = PlanChangeKind.classify(
          from_plan_slug: @sub.plan.slug,
          from_interval: @sub.billing_interval,
          to_plan_slug: @plan_slug,
          to_interval: interval
        )

        case kind
        when :upgrade
          perform_upgrade!(interval: interval)
        when :downgrade
          ScheduleDowngrade.call(club: @club, plan_slug: @plan_slug, interval: interval)
        else
          raise "Sei già su questo piano e intervallo di fatturazione"
        end
      end

      private

      def perform_upgrade!(interval:)
        release_schedule_if_any!

        price_id = PriceCatalog.price_id(plan_slug: @plan_slug, interval: interval)
        stripe_sub = ::Stripe::Subscription.retrieve(@sub.stripe_subscription_id)
        item_id = stripe_sub.items.data.first.id

        updated = ::Stripe::Subscription.update(
          stripe_sub.id,
          items: [{ id: item_id, price: price_id }],
          metadata: { club_id: @club.id, plan_slug: @plan_slug, billing_interval: interval },
          proration_behavior: "always_invoice",
          payment_behavior: "error_if_incomplete"
        )

        invoice = pay_open_subscription_invoice!(updated.id)
        apply_local_plan!(updated, interval: interval)

        ChangePlanResult.new(
          kind: :upgrade,
          plan_slug: @plan_slug,
          interval: interval,
          effective_at: Time.current,
          amount_cents: invoice&.amount_paid,
          currency: invoice&.currency,
          invoice_id: invoice&.id
        )
      end

      def release_schedule_if_any!
        return if @sub.stripe_schedule_id.blank?

        ::Stripe::SubscriptionSchedule.release(@sub.stripe_schedule_id)
      rescue ::Stripe::InvalidRequestError
        nil
      ensure
        @sub.update!(stripe_schedule_id: nil, pending_plan_id: nil, pending_billing_interval: nil)
      end

      def pay_open_subscription_invoice!(stripe_sub_id)
        invoice = ::Stripe::Invoice.list(subscription: stripe_sub_id, status: "open", limit: 1).data.first
        return nil unless invoice

        paid = invoice.status == "paid" ? invoice : ::Stripe::Invoice.pay(invoice.id)
        RecordPayment.call(stripe_invoice: paid, club: @club) if paid.status == "paid"
        paid
      rescue ::Stripe::CardError => e
        raise ::Stripe::CardError, "Pagamento non riuscito: #{e.message}"
      end

      def resolve_interval(sub)
        return PriceCatalog.normalize_interval(@interval) if @interval.present?

        sub.billing_interval.presence || PriceCatalog::DEFAULT_INTERVAL
      end

      def apply_local_plan!(stripe_sub, interval:)
        period_start, period_end = SubscriptionPeriod.times(stripe_sub)
        Billing::AssignPlan.call(
          club: @club,
          plan_slug: @plan_slug,
          status: map_status(stripe_sub.status),
          stripe_attrs: {
            stripe_customer_id: stripe_sub.customer,
            stripe_subscription_id: stripe_sub.id,
            current_period_start: period_start,
            current_period_end: period_end,
            cancel_at_period_end: stripe_sub.cancel_at_period_end,
            billing_interval: interval,
            pending_plan_id: nil,
            pending_billing_interval: nil,
            stripe_schedule_id: nil
          }
        )
      end

      def map_status(stripe_status)
        case stripe_status
        when "trialing" then "trialing"
        when "active" then "active"
        when "past_due", "unpaid" then "past_due"
        when "canceled", "incomplete_expired" then "canceled"
        else "incomplete"
        end
      end
    end
  end
end
