module JamRuby
  class Charge < ActiveRecord::Base

    attr_accessor :stripe_charge

    belongs_to :user, class_name: "JamRuby::User"

    validates :sent_billing_notices, inclusion: {in: [true, false]}

    def max_retries
      raise "not implemented"
    end
    def do_charge(force)
      raise "not implemented"
    end
    def do_send_notices
      raise "not implemented"
    end
    def do_send_unable_charge
      raise "not implemented"
    end
    def charge_retry_hours
      24
    end
    def charged_user
      raise "not implemented"
    end

    def record_charge(stripe_charge)
      self.stripe_charge_id = stripe_charge.id
      self.billed = true
      self.billed_at = Time.now
      self.save(validate: false)
    end

    def admin_url
      APP_CONFIG.admin_root_url + "/admin/charges/" + id
    end

    def charge(force = false)

      @stripe_charge = nil

      if !self.billed

        # check if we can bill at the moment
        if !force &&  last_billing_attempt_at && (charge_retry_hours.hours.ago < last_billing_attempt_at)
          return false
        end

        if !force && !billing_should_retry
          return false
        end


        # bill the user right now. if it fails, move on; will be tried again
        self.billing_attempts = self.billing_attempts + 1
        self.billing_should_retry = self.billing_attempts < max_retries
        self.last_billing_attempt_at = Time.now
        self.save(validate: false)

        begin

          stripe_charge = do_charge(force)

          # record the charge in this context (meaning, in our transaction)
          record_charge(@stripe_charge) if @stripe_charge

        rescue Stripe::StripeError => e

          stripe_handler(e)

          subject = "Unable to charge user #{charged_user.email} for lesson #{self.id} (stripe)"
          body = "user=#{charged_user.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
          AdminMailer.alerts({subject: subject, body: body}).deliver_now
          do_send_unable_charge

          return false
        rescue Exception => e

          # record the charge even if there was an unhandled exception at some point
          record_charge(@stripe_charge) if @stripe_charge

          unhandled_handler(e)

          subject = "Unable to charge user #{charged_user.email} for lesson #{self.id} (unhandled)"
          body = "user=#{charged_user.email}\n\nbilling_error_reason=#{billing_error_reason}\n\nbilling_error_detail = #{billing_error_detail}"
          AdminMailer.alerts({subject: subject, body: body}).deliver_now
          return false
        end

      end

      if !self.sent_billing_notices
        # If the charge is successful, then we post the charge to the student’s payment history,
        # and associate the charge with the lesson, so that everyone knows the student has paid, and we send an email

        do_send_notices

        self.sent_billing_notices = true
        self.sent_billing_notices_at = Time.now
        self.post_processed = true
        self.post_processed_at = Time.now
        self.save(validate: false)
      end

      return stripe_charge
    end

    def unhandled_handler(e)
      self.billing_error_reason = e.to_s
      if e.cause
        self.billing_error_detail = e.cause.to_s + "\n" + e.cause.backtrace.join("\n\t") if e.cause.backtrace
        self.billing_error_detail << "\n\n"
        self.billing_error_detail << e.to_s + "\n" + e.backtrace.join("\n\t") if e.backtrace
      else
        self.billing_error_detail = e.to_s + "\n" + e.backtrace.join("\n\t") if e.backtrace
      end
      #puts "Charge: unhandled exception #{billing_error_reason}, #{billing_error_detail}"
      self.save(validate: false)
    end

    def is_card_declined?
      billed == false && billing_error_reason == 'card_declined'
    end

    def is_card_expired?
      billed == false && billing_error_reason == 'card_expired'
    end

    def last_billed_at_date
      last_billing_attempt_at.strftime("%B %d, %Y") if last_billing_attempt_at
    end
    def stripe_handler(e)

      msg = e.to_s

      if msg.include?('declined')
        self.billing_error_reason = 'card_declined'
        self.billing_error_detail = msg
      elsif msg.include?('expired')
        self.billing_error_reason = 'card_expired'
        self.billing_error_detail = msg
      elsif msg.include?('processing')
        self.billing_error_reason = 'processing_error'
        self.billing_error_detail = msg
      else
        self.billing_error_reason = 'stripe'
        self.billing_error_detail = msg
      end

      self.save(validate: false)
    end
  end
end