Spree::Order.class_eval do
  def self.build_from_api(user, params)
    completed_at = params.delete(:completed_at)
    line_items = params.delete(:line_items_attributes) || {}
    shipments = params.delete(:shipments_attributes) || []
    payments = params.delete(:payments_attributes) || []
    adjustments = params.delete(:adjustments_attributes) || []

    ensure_country_id_from_api params[:ship_address_attributes]
    ensure_state_id_from_api params[:ship_address_attributes]

    ensure_country_id_from_api params[:bill_address_attributes]
    ensure_state_id_from_api params[:bill_address_attributes]

    order = create!(params)
    order.associate_user!(user)

    order.create_shipments_from_api(shipments)
    order.create_line_items_from_api(line_items)
    order.create_adjustments_from_api(adjustments)
    order.create_payments_from_api(payments)
    order.complete_from_api(completed_at)

    order.save!
    order.reload
  end

  def complete_from_api(completed_at)
    if completed_at
      self.completed_at = completed_at
      self.state = 'complete'
    end
  end

  def create_shipments_from_api(shipments_hash)
    shipments_hash.each do |s|
      begin
        shipment = shipments.build
        shipment.tracking = s[:tracking]

        inventory_units = s[:inventory_units] || []
        inventory_units.each do |iu|
          self.class.ensure_variant_id_from_api(iu)

          unit = shipment.inventory_units.build
          unit.order = self
          unit.variant_id = iu[:variant_id]
        end

        shipment.save!

        shipping_method = Spree::ShippingMethod.find_by_name!(s[:shipping_method])
        shipment.shipping_rates.create!(:shipping_method => shipping_method,
                                        :cost => s[:cost])
      rescue Exception => e
        raise "#{e.message} #{s}"
      end
    end
  end

  def create_payments_from_api(payments_hash)
    payments_hash.each do |p|
      begin
        payment = payments.build
        payment.amount = p[:amount].to_f
        payment.state = p.fetch(:state, 'completed')
        payment.payment_method = Spree::PaymentMethod.find_by_name!(p[:payment_method])
        payment.save!
      rescue Exception => e
        raise "#{e.message} #{p}"
      end
    end
  end

  def create_line_items_from_api(line_items_hash)
    line_items_hash.each_key do |k|
      begin
        line_item = line_items_hash[k]
        self.class.ensure_variant_id_from_api(line_item)

        extra_params = line_item.except(:variant_id, :quantity)
        line_item = self.contents.add(Spree::Variant.find(line_item[:variant_id]), line_item[:quantity])
        line_item.update_attributes(extra_params) unless extra_params.empty?
      rescue Exception => e
        raise "#{e.message} #{line_item}"
      end
    end
  end

  def create_adjustments_from_api(adjustments)
    adjustments.each do |a|
      begin
        adjustment = self.adjustments.build(:amount => a['amount'].to_f,
                                            :label => a['label'])
        adjustment.save!
        adjustment.finalize!
      rescue Exception => e
        raise "#{e.message} #{a}"
      end
    end
  end

  def self.ensure_variant_id_from_api(hash)
    begin
      unless hash[:variant_id].present?
        hash[:variant_id] = Spree::Variant.active.find_by_sku!(hash[:sku]).id
        hash.delete(:sku)
      end
    rescue Exception => e
      raise "#{e.message} #{hash}"
    end
  end

  def self.ensure_country_id_from_api(address)
    return if address.nil? or address[:country_id].present? or address[:country].nil?

    begin
      search = {}
      if name = address[:country]['name']
        search[:name] = name
      elsif iso_name = address[:country]['iso_name']
        search[:iso_name] = iso_name.upcase
      elsif iso = address[:country]['iso']
        search[:iso] = iso.upcase
      elsif iso3 = address[:country]['iso3']
        search[:iso3] = iso3.upcase
      end

      address.delete(:country)
      address[:country_id] = Spree::Country.where(search).first!.id

    rescue Exception => e
      raise "#{e.message} #{search}"
    end
  end

  def self.ensure_state_id_from_api(address)
    return if address.nil? or address[:state_id].present? or address[:state].nil?

    begin
      search = {}
      if name = address[:state]['name']
        search[:name] = name
      elsif abbr = address[:state]['abbr']
        search[:abbr] = abbr.upcase
      end

      address.delete(:state)
      address[:state_id] = Spree::State.where(search).first!.id
    rescue Exception => e
      raise "#{e.message} #{search}"
    end
  end

  def update_line_items(line_item_params)
    return if line_item_params.blank?
    line_item_params.each do |id, attributes|
      self.line_items.find(id).update_attributes!(attributes)
    end
    self.ensure_updated_shipments
  end
end