require "pay/billable/sync_email"

module Pay
  module Billable
    extend ActiveSupport::Concern

    # Keep track of which Billable models we have
    class << self
      attr_reader :includers
    end

    def self.included(base = nil, &block)
      @includers ||= []
      @includers << base if base
      super
    end

    included do |base|
      include Pay::Billable::SyncEmail
      include Pay::Stripe::Billable if defined? ::Stripe
      include Pay::Braintree::Billable if defined? ::Braintree
      include Pay::Paddle::Billable if defined? ::PaddlePay

      has_many :charges, class_name: Pay.chargeable_class, foreign_key: :owner_id, inverse_of: :owner, as: :owner
      has_many :subscriptions, class_name: Pay.subscription_class, foreign_key: :owner_id, inverse_of: :owner, as: :owner

      attribute :plan, :string
      attribute :quantity, :integer
      attribute :card_token, :string
    end

    def processor=(value)
      super(value)
      self.processor_id = nil if processor_changed?
    end

    def customer
      check_for_processor
      raise Pay::Error, "Email is required to create a customer" if email.nil?

      customer = send("#{processor}_customer")
      update_card(card_token) if card_token.present?
      customer
    end

    def customer_name
      [try(:first_name), try(:last_name)].compact.join(" ")
    end

    def charge(amount_in_cents, options = {})
      check_for_processor
      send("create_#{processor}_charge", amount_in_cents, options)
    end

    def subscribe(name: "default", plan: "default", **options)
      check_for_processor
      send("create_#{processor}_subscription", name, plan, options)
    end

    def update_card(token)
      check_for_processor
      customer if processor_id.nil?
      send("update_#{processor}_card", token)
    end

    def on_trial?(name: "default", plan: nil)
      return true if default_generic_trial?(name, plan)

      sub = subscription(name: name)
      return sub&.on_trial? if plan.nil?

      sub&.on_trial? && sub.processor_plan == plan
    end

    def on_generic_trial?
      trial_ends_at? && trial_ends_at > Time.zone.now
    end

    def processor_subscription(subscription_id, options = {})
      check_for_processor
      send("#{processor}_subscription", subscription_id, options)
    end

    def subscribed?(name: "default", processor_plan: nil)
      subscription = subscription(name: name)

      return false if subscription.nil?
      return subscription.active? if processor_plan.nil?

      subscription.active? && subscription.processor_plan == processor_plan
    end

    def on_trial_or_subscribed?(name: "default", processor_plan: nil)
      on_trial?(name: name, plan: processor_plan) ||
        subscribed?(name: name, processor_plan: processor_plan)
    end

    def subscription(name: "default")
      subscriptions.for_name(name).last
    end

    def invoice!(options = {})
      send("#{processor}_invoice!", options)
    end

    def upcoming_invoice
      send("#{processor}_upcoming_invoice")
    end

    def stripe?
      processor == "stripe"
    end

    def braintree?
      processor == "braintree"
    end

    def paypal?
      braintree? && card_type == "PayPal"
    end

    def paddle?
      processor == "paddle"
    end

    def has_incomplete_payment?(name: "default")
      subscription(name: name)&.has_incomplete_payment?
    end

    private

    def check_for_processor
      raise StandardError, "No payment processor selected. Make sure to set the #{self.class.name}'s `processor` attribute to either 'stripe' or 'braintree'." unless processor
    end

    # Used for creating a Pay::Subscription in the database
    def create_subscription(subscription, processor, name, plan, options = {})
      options[:quantity] ||= 1

      options.merge!(
        name: name || "default",
        processor: processor,
        processor_id: subscription.id,
        processor_plan: plan,
        trial_ends_at: send("#{processor}_trial_end_date", subscription),
        ends_at: nil
      )
      subscriptions.create!(options)
    end

    def default_generic_trial?(name, plan)
      # Generic trials don't have plans or custom names
      plan.nil? && name == "default" && on_generic_trial?
    end
  end
end