# When an Order is first initialized it is done in the pending status
# - when it's in the pending status, none of the buyer entered information is required
# - when a pending order is rendered:
# - if the user has a billing address, go to step 2
# - if the user has no billing address, go to step 1
#
# After Step1, we go to the confirmed status
# After Step2, we are in the purchased or declined status

module Effective
  class Order < ActiveRecord::Base
    self.table_name = EffectiveOrders.orders_table_name.to_s

    # Effective Resources
    acts_as_statused(
      :pending,         # New orders are created in a pending state
      :confirmed,       # Once the order has passed checkout step 1
      :deferred,        # Deferred providers. cheque, etransfer or phone was selected.
      :purchased,       # Purchased by provider
      :declined,        # Declined by provider
      :voided,          # Voided by admin
      :abandoned        # Not set by this gem. Can be set outside it.
    )

    # Effective Addresses
    acts_as_addressable(billing: { singular: true }, shipping: { singular: true })

    # Effective Logging
    log_changes if respond_to?(:log_changes)

    # Effective Obfuscation
    if EffectiveOrders.obfuscate_order_ids
      raise('unsupported obfuscation with tenant') if defined?(Tenant)
      acts_as_obfuscated format: '###-####-###'
    end

    # Effective Reports
    acts_as_reportable if respond_to?(:acts_as_reportable)

    attr_accessor :terms_and_conditions # Yes, I agree to the terms and conditions
    attr_accessor :confirmed_checkout   # Set on the Checkout Step 1

    # Settings in the /admin action forms
    attr_accessor :send_payment_request_to_buyer # Set by Admin::Orders#new. Should the payment request email be sent after creating an order?
    attr_accessor :send_mark_as_paid_email_to_buyer  # Set by Admin::Orders#mark_as_paid
    attr_accessor :skip_buyer_validations # Set by Admin::Orders#create

    # If we want to use orders in a has_many way
    belongs_to :parent, polymorphic: true, optional: true

    # This is user the order is for
    belongs_to :user, polymorphic: true, optional: true, validate: false
    accepts_nested_attributes_for :user, allow_destroy: false, update_only: true

    # When an organization is present, any user with role :billing in that organization can purchase this order
    belongs_to :organization, polymorphic: true, optional: true, validate: false
    accepts_nested_attributes_for :organization, allow_destroy: false, update_only: true

    # When purchased, this is the user that purchased it.
    belongs_to :purchased_by, polymorphic: true, optional: true, validate: false

    has_many :order_items, -> { order(:id) }, inverse_of: :order, dependent: :delete_all
    accepts_nested_attributes_for :order_items, allow_destroy: true, reject_if: :all_blank

    # Attributes
    effective_resource do
      # Acts as Statused
      status            :string
      status_steps      :text

      purchased_at      :datetime

      note              :text   # From buyer to admin
      note_to_buyer     :text   # From admin to buyer
      note_internal     :text   # Internal admin only

      billing_name      :string   # name of buyer
      email             :string   # same as user.email
      cc                :string   # can be set by admin

      payment           :text     # serialized hash containing all the payment details.
      payment_provider  :string
      payment_card      :string

      tax_rate            :decimal, precision: 6, scale: 3
      surcharge_percent   :decimal, precision: 6, scale: 3

      subtotal          :integer   # Sum of items subtotal
      tax               :integer   # Tax on subtotal
      amount_owing      :integer   # Subtotal + Tax

      surcharge         :integer   # Credit Card Surcharge
      surcharge_tax     :integer   # Tax on surcharge

      total             :integer   # Subtotal + Tax + Surcharge + Surcharge Tax

      timestamps
    end

    serialize :payment, Hash

    scope :deep, -> { includes(:addresses, :user, :purchased_by, :organization, order_items: :purchasable) }
    scope :sorted, -> { order(:id) }

    scope :purchased, -> { where(status: :purchased) }
    scope :purchased_or_deferred, -> { where(status: [:purchased, :deferred]) }

    scope :purchased_by, lambda { |user| purchased.where(user: user) }

    scope :not_purchased, -> { where.not(status: [:purchased, :deferred]) }
    scope :was_not_purchased, -> { where.not(status: :purchased) }

    scope :pending, -> { where(status: :pending) }
    scope :confirmed, -> { where(status: :confirmed) }
    scope :deferred, -> { where(status: :deferred) }
    scope :declined, -> { where(status: :declined) }
    scope :abandoned, -> { where(status: :abandoned) }
    scope :voided, -> { where(status: :voided) }

    scope :refunds, -> { purchased.where('total < ?', 0) }
    scope :pending_refunds, -> { not_purchased.where('total < ?', 0) }

    # effective_reports
    def reportable_scopes
      { purchased: nil, not_purchased: nil, deferred: nil, refunds: nil, pending_refunds: nil }
    end

    before_validation do
      assign_attributes(status: :confirmed) if pending? && confirmed_checkout
    end

    before_validation do
      assign_attributes(user_type: nil) if user_type.present? && user_id.blank?
      assign_attributes(organization_type: nil) if organization_type.present? && organization_id.blank?
    end

    with_options(unless: -> { done? }) do
      before_validation { assign_organization_address }
      before_validation { assign_user_address }
      before_validation { assign_billing_name }
      before_validation { assign_billing_email }
      before_validation { assign_order_item_values }
      before_validation { assign_order_values }
      before_validation { assign_order_charges }
    end

    # Order validations
    validates :email, presence: true, email: true, if: -> { user_id.present? }  # email and cc validators are from effective_resources
    validates :cc, email_cc: true

    validates :order_items, presence: { message: 'No items are present. Please add additional items.' }

    validate do
      if EffectiveOrders.organization_enabled?
        errors.add(:base, "must have a User or #{EffectiveOrders.organization_class_name || 'Organization'}") if user_id.blank? && organization_id.blank?
      else
        errors.add(:base, "must have a User") if user_id.blank?
      end
    end

    # Price validations
    validates :subtotal, presence: true
    validates :total, presence: true, if: -> { EffectiveOrders.minimum_charge.to_i > 0 }

    validate(if: -> { total.present? && EffectiveOrders.minimum_charge.to_i > 0 }, unless: -> { purchased? || (free? && EffectiveOrders.free?) || (refund? && EffectiveOrders.refund?) }) do
      if total < EffectiveOrders.minimum_charge
        errors.add(:total, "must be $#{'%0.2f' % (EffectiveOrders.minimum_charge.to_i / 100.0)} or more. Please add additional items.")
      end
    end

    validate(if: -> { tax_rate.present? }) do
      if (tax_rate > 100.0 || (tax_rate < 0.25 && tax_rate > 0.0000))
        errors.add(:tax_rate, "is invalid. expected a value between 100.0 (100%) and 0.25 (0.25%) or 0")
      end
    end

    # User validations -- An admin skips these when working in the admin/ namespace
    with_options(unless: -> { pending? || skip_buyer_validations? || purchased? }) do
      validates :tax_rate, presence: { message: "can't be determined based on billing address" }
      validates :tax, presence: true

      validates :billing_address, presence: true, if: -> { EffectiveOrders.billing_address }
      validates :shipping_address, presence: true, if: -> { EffectiveOrders.shipping_address }
      validates :note, presence: true, if: -> { EffectiveOrders.collect_note_required }
    end

    # When Purchased
    with_options(if: -> { purchased? }) do
      validates :purchased_at, presence: true
      validates :payment, presence: true

      validates :payment_provider, presence: true
      validates :payment_card, presence: true
    end

    with_options(if: -> { deferred? }) do
      validates :payment_provider, presence: true

      validate do
        errors.add(:payment_provider, "unknown deferred payment provider") unless EffectiveOrders.deferred_providers.include?(payment_provider)
      end
    end

    validate(if: -> { was_voided? && status_changed? }) do
      errors.add(:status, "cannot update status of a voided order") unless voided?
    end

    # Sanity check
    before_save(if: -> { status_was.to_s == 'purchased' }) do
      raise('cannot unpurchase an order. try voiding instead.') unless purchased? || voided?

      raise('cannot change subtotal of a purchased order') if changes[:subtotal].present?

      raise('cannot change tax of a purchased order') if changes[:tax].present?
      raise('cannot change tax rate of a purchased order') if changes[:tax_rate].present?

      raise('cannot change surcharge of a purchased order') if changes[:surcharge].present?
      raise('cannot change surcharge percent of a purchased order') if changes[:surcharge_percent].present?

      raise('cannot change total of a purchased order') if changes[:total].present?
    end

    # Effective::Order.new()
    # Effective::Order.new(Product.first)
    # Effective::Order.new(current_cart)
    # Effective::Order.new(Effective::Order.last)

    # Effective::Order.new(items: Product.first)
    # Effective::Order.new(items: [Product.first, Product.second], user: User.first)
    # Effective::Order.new(items: Product.first, user: User.first, billing_address: Effective::Address.new, shipping_address: Effective::Address.new)

    def initialize(atts = nil, &block)
      super(status: :pending) # Initialize with status pending

      return self unless atts.present?

      if atts.kind_of?(Hash)
        items = Array(atts[:item]) + Array(atts[:items])

        self.user = atts[:user] || items.first.try(:user)

        if (address = atts[:billing_address]).present?
          self.billing_address = address
          self.billing_address.full_name ||= user.to_s.presence
        end

        if (address = atts[:shipping_address]).present?
          self.shipping_address = address
          self.shipping_address.full_name ||= user.to_s.presence
        end

        atts.except(:item, :items, :user, :billing_address, :shipping_address).each do |key, value|
          self.send("#{key}=", value)
        end

        add(items) if items.present?
      else # Attributes are not a Hash
        self.user = atts.user if atts.respond_to?(:user)
        add(atts)
      end

      self
    end

    def remove(*items)
      raise 'unable to alter a purchased order' if purchased?
      raise 'unable to alter a declined order' if declined?

      removed = items.map do |item|
        order_item = if item.kind_of?(Effective::OrderItem)
          order_items.find { |oi| oi == item }
        else
          order_items.find { |oi| oi.purchasable == item }
        end

        raise("Unable to find order item for #{item}") if order_item.blank?
        order_item
      end

      removed.each { |order_item| order_item.mark_for_destruction }

      # Make sure to reset stored aggregates
      assign_attributes(subtotal: nil, tax_rate: nil, tax: nil, amount_owing: nil, surcharge_percent: nil, surcharge: nil, total: nil)

      removed.length == 1 ? removed.first : removed
    end

    # Items can be an Effective::Cart, an Effective::order, a single acts_as_purchasable, or multiple acts_as_purchasables
    # add(Product.first) => returns an Effective::OrderItem
    # add(Product.first, current_cart) => returns an array of Effective::OrderItems
    def add(*items, quantity: 1)
      raise 'unable to alter a purchased order' if purchased?
      raise 'unable to alter a declined order' if declined?

      cart_items = items.flatten.flat_map do |item|
        if item.kind_of?(Effective::Cart)
          item.cart_items.to_a
        elsif item.kind_of?(ActsAsPurchasable)
          Effective::CartItem.new(quantity: quantity, purchasable: item)
        elsif item.kind_of?(Effective::Order)
          # Duplicate an existing order
          self.note_to_buyer ||= item.note_to_buyer
          self.note_internal ||= item.note_internal
          self.cc ||= item.cc

          item.order_items.select { |oi| oi.purchasable.kind_of?(Effective::Product) }.map do |oi|
            purchasable = oi.purchasable

            product = Effective::Product.new(name: purchasable.purchasable_name, price: purchasable.price, tax_exempt: purchasable.tax_exempt)

            # Copy over any extended attributes that may have been created
            atts = purchasable.dup.attributes.except('name', 'price', 'tax_exempt', 'purchased_order_id').compact

            atts.each do |k, v|
              next unless product.respond_to?("#{k}=") && product.respond_to?(k)
              product.send("#{k}=", v) if product.send(k).blank?
            end

            Effective::CartItem.new(quantity: oi.quantity, purchasable: product)
          end
        else
          raise 'add() expects one or more acts_as_purchasable objects, or an Effective::Cart'
        end
      end.compact

      # Make sure to reset stored aggregates
      assign_attributes(subtotal: nil, tax_rate: nil, tax: nil, amount_owing: nil, surcharge_percent: nil, surcharge: nil, total: nil)

      retval = cart_items.map do |item|
        order_items.build(
          name: item.name,
          quantity: item.quantity,
          price: item.price,
          tax_exempt: (item.tax_exempt || false),
        ).tap { |order_item| order_item.purchasable = item.purchasable }
      end

      retval.size == 1 ? retval.first : retval
    end

    def update_prices!
      raise('already purchased') if purchased?
      raise('must be pending or confirmed') unless pending? || confirmed?

      present_order_items.each do |item|
        purchasable = item.purchasable

        if purchasable.blank? || purchasable.marked_for_destruction?
          item.mark_for_destruction
        else
          item.assign_purchasable_attributes
        end
      end

      save!
    end

    def to_s
      [label, ' #', to_param].join
    end

    def label
      if refund? && purchased?
        'Refund'
      elsif purchased?
        'Receipt'
      elsif refund? && (pending? || confirmed?)
        'Pending Refund'
      elsif (pending? || confirmed?)
        'Pending Order'
      else
        'Order'
      end
    end

    def total_label
      purchased? ? 'Total Paid' : 'Total Due'
    end

    # Visa - 1234
    def payment_method
      return nil unless purchased?

      provider = payment_provider if ['cheque', 'etransfer', 'phone', 'credit card'].include?(payment_provider)

      # Normalize payment card
      card = case payment_card.to_s.downcase.gsub(' ', '').strip
        when '' then nil
        when 'v', 'visa' then 'Visa'
        when 'm', 'mc', 'master', 'mastercard' then 'MasterCard'
        when 'a', 'ax', 'american', 'americanexpress' then 'American Express'
        when 'd', 'discover' then 'Discover'
        else payment_card.to_s
      end

      # Try again
      if card == 'none' && payment['card_type'].present?
        card = case payment['card_type'].to_s.downcase.gsub(' ', '').strip
          when '' then nil
          when 'v', 'visa' then 'Visa'
          when 'm', 'mc', 'master', 'mastercard' then 'MasterCard'
          when 'a', 'ax', 'american', 'americanexpress' then 'American Express'
          when 'd', 'discover' then 'Discover'
          else payment_card.to_s
        end
      end

      last4 = if payment[:active_card] && payment[:active_card].include?('**** **** ****')
        payment[:active_card][15,4]
      end

      last4 ||= if payment['active_card'] && payment['active_card'].include?('**** **** ****')
        payment['active_card'][15,4]
      end

      # stripe, moneris, moneris_checkout
      last4 ||= (payment['f4l4'] || payment['first6last4']).to_s.last(4)

      [provider.presence, card.presence, last4.presence].compact.join(' - ')
    end

    def duplicate
      Effective::Order.new(self)
    end

    # For moneris and moneris_checkout. Just a unique value. Must be 50 characters or fewer or will raise moneris error.
    def transaction_id
      [to_param, billing_name.to_s.parameterize.first(20).presence, Time.zone.now.to_i, rand(1000..9999)].compact.join('-')
    end

    def billing_first_name
      billing_name.to_s.split(' ').first
    end

    def billing_last_name
      Array(billing_name.to_s.split(' ')[1..-1]).join(' ')
    end

    def in_progress?
      pending? || confirmed? || deferred?
    end

    def done?
      persisted? && (purchased? || declined? || voided? || abandoned?)
    end

    # A custom order is one that was created by an admin
    # We allow custom orders to have their order items updated
    def custom_order?
      order_items.all? { |oi| oi.purchasable_type == 'Effective::Product' }
    end

    def purchased?(provider = nil)
      return false if (status.to_sym != :purchased)
      return true if provider.nil? || payment_provider == provider.to_s
      false
    end

    def purchased_with_credit_card?
      purchased? && EffectiveOrders.credit_card_payment_providers.include?(payment_provider)
    end

    def purchased_without_credit_card?
      purchased? && EffectiveOrders.credit_card_payment_providers.exclude?(payment_provider)
    end

    def purchasables
      present_order_items.map { |order_item| order_item.purchasable }.compact
    end

    def subtotal
      self[:subtotal] || get_subtotal()
    end

    def tax_rate
      self[:tax_rate] || get_tax_rate()
    end

    def tax
      self[:tax] || get_tax()
    end

    def amount_owing
      self[:amount_owing] || get_amount_owing()
    end

    def surcharge_percent
      self[:surcharge_percent] || get_surcharge_percent()
    end

    def surcharge
      self[:surcharge] || get_surcharge()
    end

    def surcharge_tax
      self[:surcharge_tax] || get_surcharge_tax()
    end

    def total
      self[:total] || get_total()
    end

    def total_with_surcharge
      get_total_with_surcharge()
    end

    def total_without_surcharge
      get_total_without_surcharge()
    end

    def free?
      total == 0
    end

    def refund?
      total.to_i < 0
    end

    def pending_refund?
      return false if EffectiveOrders.buyer_purchases_refund?
      return false if purchased?

      refund?
    end

    def num_items
      present_order_items.map { |oi| oi.quantity }.sum
    end

    def send_order_receipt_to_admin?
      return false if free? && !EffectiveOrders.send_order_receipts_when_free
      EffectiveOrders.send_order_receipt_to_admin
    end

    def send_order_receipt_to_buyer?
      return false if free? && !EffectiveOrders.send_order_receipts_when_free
      EffectiveOrders.send_order_receipt_to_buyer
    end

    def send_payment_request_to_buyer?
      return false if free? && !EffectiveOrders.send_order_receipts_when_free
      return false if refund?

      EffectiveResources.truthy?(send_payment_request_to_buyer)
    end

    def send_refund_notification_to_admin?
      return false unless refund?
      EffectiveOrders.send_refund_notification_to_admin
    end

    def send_mark_as_paid_email_to_buyer?
      EffectiveResources.truthy?(send_mark_as_paid_email_to_buyer)
    end

    def skip_buyer_validations?
      EffectiveResources.truthy?(skip_buyer_validations)
    end

    # This is called from admin/orders#create
    # This is intended for use as an admin action only
    # It skips any address or bad user validations
    # It's basically the same as save! on a new order, except it might send the payment request to buyer
    def pending!
      return false if purchased?

      assign_attributes(status: :pending)
      self.addresses.clear if addresses.any? { |address| address.valid? == false }
      save!

      if send_payment_request_to_buyer?
        after_commit { send_payment_request_to_buyer! }
      end

      true
    end

    # Used by admin checkout only
    def confirm!
      return false if purchased?
      confirmed!
    end

    # This lets us skip to the confirmed workflow for an admin...
    def assign_confirmed_if_valid!
      return unless pending?

      assign_attributes(status: :confirmed)
      return true if valid?

      self.errors.clear
      assign_attributes(status: :pending)
      false
    end

    # Called by effective_memberships to update prices from purchasable fees
    # Not called internally
    def update_purchasable_attributes
      present_order_items.each { |oi| oi.update_purchasable_attributes }
    end

    # Call this as a way to skip over non consequential orders
    # And mark some purchasables purchased
    # This is different than the Mark as Paid payment processor
    def mark_as_purchased!(current_user: nil)
      purchase!(skip_buyer_validations: true, email: false, skip_quickbooks: true, current_user: current_user)
    end

    # Effective::Order.new(items: Product.first, user: User.first).purchase!(email: false)
    def purchase!(payment: nil, provider: nil, card: nil, email: true, skip_buyer_validations: false, skip_quickbooks: false, current_user: nil)
      return true if purchased?

      raise('unable to purchase voided order') if voided?

      # Assign attributes
      assign_attributes(
        skip_buyer_validations: skip_buyer_validations,

        status: :purchased,
        purchased_at: (purchased_at.presence || Time.zone.now),
        purchased_by: (purchased_by.presence || current_user),

        payment: payment_to_h(payment.presence || 'none'),
        payment_provider: (provider.presence || 'none'),
        payment_card: (card.presence || 'none')
      )

      if current_user&.email.present?
        assign_attributes(email: current_user.email)
      end

      # Updates surcharge and total based on payment_provider
      assign_order_charges()

      begin
        Effective::Order.transaction do
          run_purchasable_callbacks(:before_purchase)

          save!
          update_purchasables_purchased_order!

          run_purchasable_callbacks(:after_purchase)
        end
      rescue ActiveRecord::RecordInvalid => e
        Effective::Order.transaction do
          save!(validate: false)
          update_purchasables_purchased_order!
        end

        raise(e)
      end

      send_order_receipts! if email
      after_commit { sync_quickbooks!(skip: skip_quickbooks) }

      true
    end

    # We support two different Quickbooks synchronization gems: effective_qb_sync and effective_qb_online
    def sync_quickbooks!(skip:)
      if EffectiveOrders.qb_online?
        skip ? EffectiveQbOnline.skip_order!(self) : EffectiveQbOnline.sync_order!(self)
      end

      if EffectiveOrders.qb_sync?
        skip ? EffectiveQbSync.skip_order!(self) : true # Nothing to do
      end

      true
    end

    def skip_quickbooks!
      sync_quickbooks!(skip: true)
    end

    def defer!(provider: 'none', email: true)
      return false if purchased?

      assign_attributes(payment_provider: provider)
      deferred!

      send_payment_request_to_buyer! if email

      true
    end

    def decline!(payment: 'none', provider: 'none', card: 'none', validate: true)
      return false if declined?

      raise('order already purchased') if purchased?

      error = nil

      assign_attributes(
        skip_buyer_validations: true,

        status: :declined,
        purchased_at: nil,
        purchased_by: nil,

        payment: payment_to_h(payment),
        payment_provider: provider,
        payment_card: (card.presence || 'none')
      )

      Effective::Order.transaction do
        begin
          run_purchasable_callbacks(:before_decline)
          save!(validate: validate)
          run_purchasable_callbacks(:after_decline)
        rescue ActiveRecord::RecordInvalid => e
          self.status = status_was

          error = e.message
          raise ::ActiveRecord::Rollback
        end
      end

      raise "Failed to decline order: #{error || errors.full_messages.to_sentence}" unless error.nil?

      true
    end

    def void!
      raise('already voided') if voided?
      voided!(skip_buyer_validations: true)
    end

    def unvoid!
      raise('order must be voided to unvoid') unless voided?
      unvoided!(skip_buyer_validations: true)
    end

    # These are all the emails we send all notifications to
    def emails
      ([purchased_by.try(:email)] + [email] + [user.try(:email)] + Array(organization.try(:billing_emails))).map(&:presence).compact.uniq
    end

    # Doesn't control anything. Purely for the flash messaging
    def emails_send_to
      (emails + [cc.presence]).compact.uniq.to_sentence
    end

    def send_order_receipts!
      send_order_receipt_to_admin! if send_order_receipt_to_admin?
      send_order_receipt_to_buyer! if send_order_receipt_to_buyer?
      send_refund_notification! if send_refund_notification_to_admin?
    end

    def send_order_receipt_to_admin!
      EffectiveOrders.send_email(:order_receipt_to_admin, self) if purchased?
    end

    def send_order_receipt_to_buyer!
      EffectiveOrders.send_email(:order_receipt_to_buyer, self) if purchased?
    end
    alias_method :send_buyer_receipt!, :send_order_receipt_to_buyer!

    def send_payment_request_to_buyer!
      EffectiveOrders.send_email(:payment_request_to_buyer, self) unless purchased?
    end

    def send_pending_order_invoice_to_buyer!
      EffectiveOrders.send_email(:pending_order_invoice_to_buyer, self) unless purchased?
    end

    def send_refund_notification!
      EffectiveOrders.send_email(:refund_notification_to_admin, self) if refund?
    end

    protected

    def get_subtotal
      present_order_items.map { |oi| oi.subtotal }.sum
    end

    def get_tax_rate
      rate = instance_exec(self, &EffectiveOrders.order_tax_rate_method).to_f

      if (rate > 100.0 || (rate < 0.25 && rate > 0.0000))
        raise "expected EffectiveOrders.order_tax_rate_method to return a value between 100.0 (100%) and 0.25 (0.25%) or 0 or nil. Received #{rate}. Please return 5.25 for 5.25% tax."
      end

      rate
    end

    def get_tax
      return 0 unless tax_rate.present?
      present_order_items.reject { |oi| oi.tax_exempt? }.map { |oi| (oi.subtotal * (tax_rate / 100.0)).round(0).to_i }.sum
    end

    def get_amount_owing
      subtotal + tax
    end

    def get_surcharge_percent
      percent = EffectiveOrders.credit_card_surcharge_percent.to_f
      return nil unless percent > 0.0

      return 0.0 if purchased_without_credit_card?

      if (percent > 10.0 || percent < 0.5)
        raise "expected EffectiveOrders.credit_card_surcharge to return a value between 10.0 (10%) and 0.5 (0.5%) or nil. Received #{percent}. Please return 2.5 for 2.5% surcharge."
      end

      percent
    end

    def get_surcharge
      return 0 unless surcharge_percent.present?
      ((subtotal + tax) * (surcharge_percent / 100.0)).round(0).to_i
    end

    def get_surcharge_tax
      return 0 unless tax_rate.present?
      (surcharge * (tax_rate / 100.0)).round(0).to_i
    end

    def get_total
      subtotal + tax + surcharge + surcharge_tax
    end

    def get_total_with_surcharge
      subtotal + tax + surcharge + surcharge_tax
    end

    def get_total_without_surcharge
      subtotal + tax
    end

    private

    def present_order_items
      order_items.reject { |oi| oi.marked_for_destruction? }
    end

    # Organization first
    def assign_billing_name
      self.billing_name = billing_address.try(:full_name).presence || organization.to_s.presence || user.to_s.presence
    end

    # User first
    def assign_billing_email
      email = emails.first
      assign_attributes(email: email) if email.present?
    end

    def assign_organization_address
      return unless organization.present?

      if EffectiveOrders.billing_address && billing_address.blank? && organization.try(:billing_address).present?
        self.billing_address = organization.billing_address
        self.billing_address.full_name ||= organization.to_s.presence
      end

      if EffectiveOrders.shipping_address && shipping_address.blank? && organization.try(:shipping_address).present?
        self.shipping_address = organization.shipping_address
        self.shipping_address.full_name ||= organization.to_s.presence
      end
    end

    def assign_user_address
      return unless user.present?

      if EffectiveOrders.billing_address && billing_address.blank? && user.try(:billing_address).present?
        self.billing_address = user.billing_address
        self.billing_address.full_name ||= user.to_s.presence
      end

      if EffectiveOrders.shipping_address && shipping_address.blank? && user.try(:shipping_address).present?
        self.shipping_address = user.shipping_address
        self.shipping_address.full_name ||= user.to_s.presence
      end
    end

    # These two overwrites the prices, taxes, surcharge, etc on every save.
    # Does not get run from the before_validate on purchase.
    def assign_order_item_values
      # Copies prices from purchasable into order items
      present_order_items.each { |oi| oi.assign_purchasable_attributes }
    end

    def assign_order_values
      # Calculated from each item
      self.subtotal = get_subtotal()

      # We only know tax if there is a billing address
      self.tax_rate = get_tax_rate()
      self.tax = get_tax()

      # Subtotal + Tax
      self.amount_owing = get_amount_owing()
    end

    def assign_order_charges
      # We only apply surcharge for credit card orders. But we have to display and calculate for non purchased orders
      self.surcharge_percent = get_surcharge_percent()
      self.surcharge = get_surcharge()
      self.surcharge_tax = get_surcharge_tax()

      # Subtotal + Tax + Surcharge + Surcharge Tax
      self.total = get_total()
    end

    def update_purchasables_purchased_order!
      purchasables.each do |purchasable| 
        columns = {
          purchased_order_id: id,
          purchased_at: (purchased_at if purchasable.respond_to?(:purchased_at=)),
          purchased_by_id: (purchased_by_id if purchasable.respond_to?(:purchased_by_id=)),
          purchased_by_type: (purchased_by_type if purchasable.respond_to?(:purchased_by_type=))
        }.compact

        purchasable.update_columns(columns)
      end

      true
    end

    def run_purchasable_callbacks(name)
      order_items.select { |item| item.purchasable.respond_to?(name) }.each do |item|
        if item.class.respond_to?(:transaction)
          item.class.transaction { item.purchasable.public_send(name, self, item) }
        else
          item.purchasable.public_send(name, self, item)
        end
      end

      if parent.respond_to?(name)
        if parent.class.respond_to?(:transaction)
          parent.class.transaction { parent.public_send(name, self) }
        else
          parent.public_send(name, self)
        end
      end

      true
    end

    def payment_to_h(payment)
      if payment.respond_to?(:to_unsafe_h)
        payment.to_unsafe_h.to_h
      elsif payment.respond_to?(:to_h)
        payment.to_h
      else
        { details: (payment.to_s.presence || 'none') }
      end
    end

  end
end