module Shoppe
  class OrderItem < ActiveRecord::Base
  
    # Set the table name
    self.table_name = 'shoppe_order_items'
  
    # Relationships
    belongs_to :order, :class_name => 'Shoppe::Order'
    belongs_to :ordered_item, :polymorphic => true
    has_many :stock_level_adjustments, :as => :parent, :dependent => :nullify, :class_name => 'Shoppe::StockLevelAdjustment'
  
    # Validations
    validates :quantity, :numericality => true
  
    before_validation do
      self.weight = self.quantity * self.ordered_item.weight
    end
  
    # This allows you to add a product to the scoped order. For example Order.first.order_items.add_product(...).
    # This will either increase the quantity of the value in the order or create a new item if one does not
    # exist already.
    def self.add_item(ordered_item, quantity = 1)
      raise Errors::UnorderableItem, :ordered_item => ordered_item unless ordered_item.orderable?
      transaction do
        if existing = self.where(:ordered_item_id => ordered_item.id, :ordered_item_type => ordered_item.class.to_s).first
          existing.increase!(quantity)
          existing
        else
          new_item = self.create(:ordered_item => ordered_item, :quantity => 0)
          new_item.increase!(quantity)
        end
      end
    end

    # This allows you to remove a product from an order. It will also ensure that the order's
    # custom delivery service is updated.
    def remove
      transaction do
        self.destroy!
        self.order.remove_delivery_service_if_invalid
      end
    end
  
  
    # Increase the quantity of items in the order by the number provided. Will raise an error if we don't have
    # the stock to do this.
    def increase!(amount = 1)
      transaction do
        self.quantity += amount
        unless self.in_stock?
          raise Shoppe::Errors::NotEnoughStock, :ordered_item => self.ordered_item, :requested_stock => self.quantity
        end
        self.save!
        self.order.remove_delivery_service_if_invalid
      end
    end
  
    # Decreases the quantity of items in the order by the number provided.
    def decrease!(amount = 1)
      transaction do
        self.quantity -= amount
        self.quantity == 0 ? self.destroy : self.save!
        self.order.remove_delivery_service_if_invalid
      end
    end
  
    # Return the unit price for the item
    def unit_price
      @unit_price ||= read_attribute(:unit_price) || ordered_item.try(:price) || 0.0
    end
  
    # Return the cost price for the item
    def unit_cost_price
      @unit_cost_price ||= read_attribute(:unit_cost_price) || ordered_item.try(:cost_price) || 0.0
    end
  
    # Return the tax rate for the item
    def tax_rate
      @tax_rate ||= read_attribute(:tax_rate) || ordered_item.try(:tax_rate).try(:rate_for, self.order) || 0.0 
    end
  
    # Return the total tax for the item
    def tax_amount
      @tax_amount ||= read_attribute(:tax_amount) || (self.sub_total / BigDecimal(100)) * self.tax_rate
    end
  
    # Return the total cost for the product
    def total_cost
      quantity * unit_cost_price
    end
  
    # Return the sub total for the product
    def sub_total
      quantity * unit_price
    end
  
    # Return the total price including tax for the order line
    def total
      tax_amount + sub_total
    end
  
    # This method will be triggered when the parent order is confirmed. This should automatically
    # update the stock levels on the source product.
    def confirm!
      write_attribute :unit_price, self.unit_price
      write_attribute :unit_cost_price, self.unit_cost_price
      write_attribute :tax_rate, self.tax_rate
      write_attribute :tax_amount, self.tax_amount
      save!
    
      if self.ordered_item.stock_control?
        self.ordered_item.stock_level_adjustments.create(:parent => self, :adjustment => 0 - self.quantity, :description => "Order ##{self.order.number} deduction")
      end
    end
  
    # This method will be trigger when the parent order is accepted.
    def accept!
    end
  
    # This method will be trigger when the parent order is rejected.
    def reject!
      self.stock_level_adjustments.destroy_all
    end
  
    # Do we have the stock needed to fulfil this order?
    def in_stock?
      if self.ordered_item.stock_control?
        self.ordered_item.stock >= self.quantity
      else
        true
      end
    end
  
    # Validate the stock level against the product and update as appropriate. This method will be executed
    # before an order is completed. If we have run out of this product, we will update the quantity to an
    # appropriate level (or remove the order item) and return the object.
    def validate_stock_levels
      if in_stock?
        false
      else
        self.quantity = self.ordered_item.stock
        self.quantity == 0 ? self.destroy : self.save!
        self
      end
    end
  
  end
end