class ClosetHanger < ApplicationRecord
  belongs_to :item
  belongs_to :list, class_name: 'ClosetList', optional: true
  belongs_to :user

  delegate :name, to: :item, prefix: true

  validates :item_id, :uniqueness => {:scope => [:user_id, :owned, :list_id]}
  validates :quantity, :numericality => {:greater_than => 0}
  validates_presence_of :item, :user

  validate :list_belongs_to_user

  scope :alphabetical_by_item_name, -> {
    it = Item::Translation.arel_table
    joins(:item => :translations).where(it[:locale].eq(I18n.locale)).
      order(it[:name].asc)
  }
  scope :newest, -> { order(arel_table[:created_at].desc) }
  scope :owned_before_wanted, -> { order(arel_table[:owned].desc) }
  scope :unlisted, -> { where(:list_id => nil) }

  {:owned => true, :wanted => false}.each do |name, owned|
    scope "#{name}_trading", -> {
      joins(:user).includes(:list).
        where(:owned => owned).
        where((
          arel_table[:list_id].eq(nil).and(
            User.arel_table["#{name}_closet_hangers_visibility"].gteq(ClosetVisibility[:trading].id)
          )
          ).or(
          ClosetList.arel_table[:visibility].gteq(ClosetVisibility[:trading].id)
        ))
      }
  end

  before_validation :merge_quantities, :set_owned_by_list

  def possibly_null_closet_list
    list || user.null_closet_list(owned)
  end

  def trading?
    possibly_null_closet_list.trading?
  end

  def possibly_null_list_id=(list_id_or_owned)
    if list_id_or_owned.to_s == 'true' || list_id_or_owned.to_s == 'false'
      self.list_id = nil
      self.owned = list_id_or_owned
    else
      self.list_id = list_id_or_owned
      # owned is set in the set_owned_by_list hook
    end
  end

  def verb(subject=:someone)
    self.class.verb(subject, owned?)
  end

  def self.verb(subject, owned, positive=true)
    base = (owned) ? 'own' : 'want'
    base << 's' if positive && subject != :you && subject != :i
    base
  end

  def self.preload_items(
    hangers,
    items_scope: Item.all,
    item_translations_scope: Item::Translation.all
  )
    # Preload the records we need. (This is like `includes`, but `includes`
    # always selects all fields for all records, and we give the caller the
    # opportunity to specify which fields it actually wants via scope!)
    items = items_scope.where(id: hangers.map(&:item_id))
    translations = item_translations_scope.where(item_id: items.map(&:id))

    # Group the records by relevant IDs.
    translations_by_item_id = translations.group_by(&:item_id)
    items_by_id = items.to_h { |i| [i.id, i] }

    # Assign the preloaded records to the records they belong to. (This is like
    # doing e.g. i.translations = ..., but that's a database write - we
    # actually just want to set the `translations` field itself directly!
    # Hacky, ripped from how `ActiveRecord::Associations::Preloader` does it!)
    items.each do |item|
      item.association(:translations).target = translations_by_item_id[item.id]
    end
    hangers.each do |hanger|
      hanger.association(:item).target = items_by_id[hanger.item_id]
    end
  end
  
  def self.set_quantity!(quantity, options)
    quantity = quantity.to_i
    conditions = {:user_id => options[:user_id].to_i,
      :item_id => options[:item_id].to_i}
    
    if options[:key] == "true"
      conditions[:owned] = true
      conditions[:list_id] = nil
    elsif options[:key] == "false"
      conditions[:owned] = false
      conditions[:list_id] = nil
    else
      conditions[:list_id] = options[:key].to_i
    end
    
    hanger = self.where(conditions).first
    
    if quantity > 0
      # If quantity is non-zero, create/update the corresponding hanger.
      
      unless hanger
        hanger = self.new
        hanger.user_id = conditions[:user_id]
        hanger.item_id = conditions[:item_id]
        # One of the following will be nil, and that's okay. If owned is nil,
        # we'll cover for it before validation, as always.
        hanger.owned   = conditions[:owned]
        hanger.list_id = conditions[:list_id]
      end
      
      hanger.quantity = quantity
      hanger.save!
    elsif hanger
      # If quantity is zero and there's a hanger, destroy it.
      hanger.destroy
    end
    
    # If quantity is zero and there's no hanger, good. Do nothing.
  end

  protected

  def list_belongs_to_user
    if list_id?
      if list
        errors.add(:list_id, "must belong to you") unless list.user_id == user_id
      else
        errors.add(:list, "must exist")
      end
    end
  end
  
  def merge_quantities
    # Find a hanger that conflicts: for the same item, in the same user's
    # closet, same owned status, same list. It also must not be the current
    # hanger. Select enough for our logic and to update flex_source.
    # TODO: We deleted flex, does this reduce what data we need here?
    conflicting_hanger = self.class.select([:id, :quantity, :user_id, :item_id,
                                            :owned]).
      where(:user_id => user_id, :item_id => item_id, :owned => owned,
        :list_id => list_id).where(['id != ?', self.id]).first
    
    # If there is such a hanger, remove it and merge its quantity into this one.
    if conflicting_hanger
      self.quantity += conflicting_hanger.quantity
      conflicting_hanger.destroy
    end
    
    true
  end

  def set_owned_by_list
    self.owned = list.hangers_owned if list
    true
  end
end