class Service < ActiveRecord::Base
  has_many :order_items
  has_many :price_points, :class_name => "ServicePricePoint" do
    # A convenience method which grabs a price point by id.
    #
    # @param [String, Numeric] id
    #
    # @return [ServicePricePoint, nil]
    def by_id(id)
      id = id.to_i
      select {|p| p.id == id}.first
    end
  end

  has_one  :current_price_point,    -> {where(current: true).order('valid_from DESC')}, :class_name => "ServicePricePoint"
  has_many :historic_price_points,  -> {where(current: false)},  :class_name => 'ServicePricePoint'

  after_save :retire_price_points

  track_user_edits
  validations_from_schema

  # All editing of price points is done via the SKU
  accepts_nested_attributes_for :price_points

  # Incoming nested price points are inspected for changes to existing price points,
  # existing points are 'expired', and new ones inserted to become the replacement point
  alias_method :original_price_points_attributes=, :price_points_attributes=

  # This exploits the ::nested_attributes_for declaration to make it easy for
  # us to update multiple price points and perform validation across them in
  # one hit.
  #
  # @param Hash incoming
  #
  # @return Hash
  def price_points_attributes=(incoming)
    incoming.each_pair do |i, attrs|
      if attrs.has_key?('id')
        current = price_points.by_id(attrs['id'])

        current.attributes = if attrs['expire'] == '1'
          {:current => false, :valid_to => Time.now}
        else
          attrs.except('id', 'expire')
        end
      else
        #ignore new incoming points with no values set
        price_points.build(attrs.merge(:valid_from => Time.now, :current => true))
      end
    end

    incoming
  end

  # Returns the service with the 'shipping' key.
  #
  # @return Service
  def self.shipping_service
    where(:key => 'shipping').first
  end

  # Shortcut to the price of the current price point
  #
  # @return SpookAndPuff::Money
  def current_price
    current_price_point.price
  end

  # The total revenue generated by this service as applied to orders
  #
  # @return SpookAndPuff::Money
  def revenue
    order_items.sum(&:total)
  end

  # Checks to see if any of the existing price points need to be replaced with
  # newer instances.
  #
  # @return nil
  def retire_price_points
    retiring = price_points.select {|s| !s.new_record? and s.changed? and s.current}

    retiring.each do |point|
      replacement = price_points.build(
        :current => true,
        :valid_from => Time.now,
        :price => point.price
      )

      raise "Replacement for price point #{point.id} is invalid" unless replacement.valid?

      point.reload
      point.attributes = {:valid_to => Time.now, :current => false}
    end

    nil
  end
end
