class Product < ActiveRecord::Base
  ##--- Asociaciones
  belongs_to :unit
  has_and_belongs_to_many :categories

  has_and_belongs_to_many :pointsales, join_table: :available_products

  has_many :sales_details
  has_many :purchase_details
  has_many :inventories_moves
  has_many :pre_sales
  has_many :pre_purchases
  has_many :special_prices
  has_many :pre_transfers
  has_many :available_products

  enum status: [:erased, :active, :inactive]

  acts_as_taggable_on :sizes, :colors, :styles
  accepts_nested_attributes_for :available_products

  audited

  attr_accessor :skip_sku_validation
  mount_uploader :img_product, ImageUploader

  ##--- Validaciones previas de guardar
  validates :sku,
            presence: { message: "Debe capturar el SKU del producto." },
            length: { maximum: 30, too_long: "El maximo de caracteres debe ser %{count}." },
            uniqueness: { message: "El SKU ya fue utilizado, favor de especificar otro." },
            unless: :skip_sku_validation
  validates_presence_of :name, message: "Debe capturar el nombre del producto."
  validates_presence_of :unit_id, message: "Debe seleccionar la unidad de medida correspondiente al producto."
  validates_presence_of :category_ids, message: "Debe elegir por lo menos una línea de producto relacionada al producto."
  validates :barcode, uniqueness: { message: "El codigo de barras ya fue utilizado, favor de especificar otro." }, allow_blank: true
  validates_presence_of :name, message: "Debe capturar el nombre del producto."
  validates_presence_of :price_sale, message: "Debe capturar el precio de venta del producto."

  def valid_categories
    categories.count > 0
  end

  def small_img
    if img_product?
      img_product.url(:medium).to_s
    else
      img = "/images/original/missing.png"
    end
  end

  ##--- Tipo de vistas / consultas
  scope :vigentes, -> { where("status != 0").order(" status ASC, name ASC") }
  scope :activos, -> { where("status = 1").order(" name ASC") }
  scope :activos_children, -> { where("status = 1 and is_parent = false ").order("products.name ASC") }
  scope :vigentes_parents, -> { where("status != 0 and parent_id IS NULL ").order(" status ASC, name ASC") }
  scope :name_sku_barcode_like, ->(name) { where("status = 1 and is_parent = false and (name ilike ? or sku ilike ? or barcode ilike ?)", "%#{name}%", "%#{name}%", "%#{name}%").order("name") }
  scope :name_sku_barcode_attribute_like, ->(name, attributes_string) { where("status = 1 and is_parent = false and (name ilike ? or sku ilike ? or barcode ilike ?) #{attributes_string}", "%#{name}%", "%#{name}%", "%#{name}%").order("name") }
  # para special_prices
  scope :name_sku_barcode_like_sp, ->(name) { where("status = 1 and is_parent = true and (name ilike ? or sku ilike ? or barcode ilike ?)", "%#{name}%", "%#{name}%", "%#{name}%").order("name") }

  def name_with_sku
    sku.to_s + " - " + name.to_s
  end

  def display_sku_name_attributes
    sku.to_s + " | " + name.to_s + " | " + display_attributes.to_s
  end

  def stock_in_pointsale(pointsale_id)
    stock = 0
    # checar si hay existencias en los almacenes.
    warehouses_stock = WarehouseStock.where(product_id: id)
    warehouses_stock.each do |warehouse|
      # solamente hacerlo cuando pointsale id sea nil porque es el index de producto
      stock += warehouse.stock if pointsale_id.zero?
    end
    # existencias en puntos de venta.
    availables = AvailableProduct.where(product_id: id)
    availables.each do |available|
      if pointsale_id == available.pointsale_id
        stock = available.stock
      elsif pointsale_id.zero?
        stock +=  available.stock
      end
    end
    stock
  end

  def can_be_deleted?
    if is_parent
      children_ids = Product.where(parent_id: id).pluck(:id)
      in_available = AvailableProduct.where("product_id IN (?) and stock > 0", children_ids).any?
      in_warehouse = WarehouseStock.where("product_id IN (?) and stock > 0", children_ids).any?
    else
      in_warehouse = WarehouseStock.where("product_id = #{id} and stock > 0").any?
      in_available = AvailableProduct.where("product_id = #{id} and stock > 0").any?
    end
    in_available == true || in_warehouse == true ? false : true
  end

  def last_sale(pointsale_id)
    unless pointsale_id.nil?
      last_time = Pointsale.find(pointsale_id).sales_details.where(product_id: id).last
    end
  end

  def available_in_pointsale?(pointsale_id)
    if pointsales.exists?(id: pointsale_id)
      true
    else
      false
    end
  end

  def get_available_in_pointsale(pointsale_id)
    AvailableProduct.find_by(pointsale_id: pointsale_id, product_id: id)
  end

  def get_price_sale(pointsale_id)
    if pointsale_id != 0 && available_in_pointsale?(pointsale_id) && !get_available_in_pointsale(pointsale_id).price_sale.nil?
      get_available_in_pointsale(pointsale_id).price_sale
    else
      price_sale
    end
  end

  def pointsales_prices
    AvailableProduct.where("product_id = ? and price_sale IS NOT NULL", id)
  end

  def variants_attributes
    ActsAsTaggableOn::Tagging.where(taggable_id: id, taggable_type: 'Product').distinct(:context).select(:context)
  end

  def get_combinations(combinations, attributes)
    if presentation

      ##--- crear el array de los arrays de atributos

      if size_list.count > 0
        attributes << size_list
      end
      if color_list.count > 0
        attributes << color_list
      end
      if style_list.count > 0
        attributes << style_list
      end

      ##--- verificar que atributos tenga mas de una categoria
      if attributes.count > 1
        ##--- Making combinations from arrays
        first_array, *rest_of_arrays = attributes
        combinations = first_array.product(*rest_of_arrays)
      else
        attributes[0].each do |attribute|
          combinations << attribute
        end
      end

      combinations
    end
  end

  # rubocop:disable Metrics/BlockLength
  def save_variants(current_user)
    Thread.new do
      combinations = Array.new
      attributes = Array.new
      combinations = get_combinations(combinations, attributes)

      ##--- recorrer combinaciones para crear las variantes de productos
      unless combinations.nil?
        combinations.each_with_index do |combination, index|
          @products_variant = Product.new
          @products_variant = dup
          @products_variant.parent_id = id
          @products_variant.is_parent = false
          @products_variant.sku = sku + (index + 1).to_s + "A"
          @products_variant.category_ids = category_ids
          attributes_json = {}

          if combination.is_a?(Array)
            combination.each do |attrib|
              attributes_json = @products_variant.assign_attributes_to_variant(attrib, id, attributes_json)
            end
          else
            attributes_json = @products_variant.assign_attributes_to_variant(combination, id, attributes_json)
          end

          @products_variant.attributes_json = attributes_json.to_json
          @products_variant.save
          if current_user.usertype == 'G'
            AvailableProduct.create(product_id: @products_variant.id, pointsale_id: current_user.pointsale_id, stock: 0)
          end
        end
      end
    end
  end
  # rubocop:enable Metrics/BlockLength

  def assign_attributes_to_variant(attri, parent_id, attributes_json)
    attri_id = ActsAsTaggableOn::Tag.where("lower(name) = lower(?)", attri).select(:id).first
    get_context = ActsAsTaggableOn::Tagging.where(tag_id: attri_id, taggable_id: parent_id, taggable_type: 'Product').select(:context).first
    context = get_context.context

    if id != parent_id
      if context == "sizes"
        self.size_list = attri.to_s
      elsif context == "colors"
        self.color_list = attri.to_s
      elsif context == "styles"
        self.style_list = attri.to_s
      end
    end
    attributes_json[context] = attri.to_s
    attributes_json
  end

  def update_attributes_to_variants(new_sizes, new_colors, new_styles)
    unless new_sizes.nil?
      (JSON.parse new_sizes).each do |s|
        sizes.each do |p|
          next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
          # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
          size_list.remove(p.name.to_s)
          variants = children.tagged_with(p.name.to_s, on: :sizes, any: true)
          variants.each do |v|
            v.size_list.remove(p.name.to_s)
            v.size_list.add(s["name"].to_s)
            v.save(validate: false)
          end
          size_list.add(s["name"].to_s)
        end
      end
    end
    unless new_colors.nil?
      (JSON.parse new_colors).each do |s|
        colors.each do |p|
          next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
          # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
          color_list.remove(p.name.to_s)
          variants = children.tagged_with(p.name.to_s, on: :colors, any: true)
          variants.each do |v|
            v.color_list.remove(p.name.to_s)
            v.color_list.add(s["name"].to_s)
            v.save(validate: false)
          end
          color_list.add(s["name"].to_s)
        end
      end
    end
    unless new_styles.nil?
      (JSON.parse new_styles).each do |s|
        styles.each do |p|
          next unless p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
          # if p.id.to_s == s["id"].to_s && p.name.to_s != s["name"].to_s
          style_list.remove(p.name.to_s)
          variants = children.tagged_with(p.name.to_s, on: :styles, any: true)
          variants.each do |v|
            v.style_list.remove(p.name.to_s)
            v.style_list.add(s["name"].to_s)
            v.save(validate: false)
          end
          style_list.add(s["name"].to_s)
        end
      end
    end

    if save(validate: false)
      children.each do |variant|
        attributes_json = {}

        attributes_json = variant.assign_attributes_to_variant(variant.size_list, id, attributes_json) unless variant.size_list.count.zero?
        attributes_json = variant.assign_attributes_to_variant(variant.color_list, id, attributes_json) unless variant.color_list.count.zero?
        attributes_json = variant.assign_attributes_to_variant(variant.style_list, id, attributes_json) unless variant.style_list.count.zero?
        variant.attributes_json = attributes_json.to_json
        variant.save(validate: false)
      end
    end
  end

  # rubocop:disable Metrics/BlockLength
  def save_new_attributes(new_sizes, new_colors, new_styles)
    Thread.new do
      combinations = Array.new
      attributes = Array.new

      unless new_sizes.nil?
        new_sizes.each do |s|
          size_list.add(s.to_s)
          save(validate: false)
        end
      end
      unless new_colors.nil?
        new_colors.each do |s|
          color_list.add(s.to_s)
          save(validate: false)
        end
      end
      unless new_styles.nil?
        new_styles.each do |s|
          style_list.add(s.to_s)
          save(validate: false)
        end
      end

      combinations = get_combinations(combinations, attributes)
      # self.save(:validate: false)
      combinations.each_with_index do |combination, index|
        attributes = {}
        if combination.is_a?(Array)
          combination.each do |c|
            attributes = assign_attributes_to_variant(c, id, attributes)
          end
        else
          attributes = assign_attributes_to_variant(combination, id, attributes)
        end

        next unless children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
        # if children.where("attributes_json = ?", attributes.to_json).select(:id).first.nil?
        @products_variant = Product.new
        @products_variant = dup
        @products_variant.parent_id = id
        @products_variant.is_parent = false
        @products_variant.sku = sku + (index + 1).to_s + "A"
        @products_variant.category_ids = category_ids
        attrs_json = {}
        if combination.is_a?(Array)
          combination.each do |attrib|
            attrs_json = @products_variant.assign_attributes_to_variant(attrib, id, attrs_json)
          end
        else
          attrs_json = @products_variant.assign_attributes_to_variant(combination, id, attrs_json)
        end
        @products_variant.attributes_json = attributes.to_json
        @products_variant.save(validate: false)
      end
    end
  end
  # rubocop:enable Metrics/BlockLength

  def children
    Product.where("parent_id = ? and status != 0 ", id)
  end

  def attributes_to_hash
    JSON.parse attributes_json.gsub('=>', ':')
  end

  def display_attributes
    attributes = ""
    unless attributes_json.nil?
      attributes_to_hash.each do |attr_, value|
        description = I18n.t("dictionary." + attr_) + ": #{value}"
        attributes = attributes.blank? ? description.to_s : attributes.to_s + " " + description.to_s
      end
    end
    attributes
  end

  def display_attributes_receipt
    attributes = ""
    unless attributes_json.nil?
      attributes_to_hash.each do |_attr_, value|
        attributes = attributes.blank? ? value.to_s : attributes.to_s + " " + value.to_s
      end
    end
    attributes.upcase
  end

  def self.most_selled_products(period, user)
    # se envia al user porque no se puede acceder al current, esto es, para que el gerente
    # vea los mas vendidos de su punto de venta.
    all_products = Array.new
    if period == 'week'
      beg_period = Date.current.beginning_of_week
      end_period = Date.current.end_of_week
    elsif period == 'month'
      beg_period = Date.current.beginning_of_month
      end_period = Date.current.end_of_month
    end

    if user.usertype == 'A'
      quantities_top_products = SalesDetail.activos.joins(:sale, :product).where("sales.date_sale between ? and ?", beg_period, end_period).group('products.name').order('sum_quantity desc').limit(10).sum(:quantity)
      total_top_products = SalesDetail.activos.joins(:sale).where('sales.date_sale between ? and ?', beg_period, end_period).order('sum_quantity desc').limit(10).sum(:quantity)
      total_sold = SalesDetail.activos.joins(:sale).where('sales.date_sale between ? and ?', beg_period, end_period).sum(:quantity)
    elsif user.usertype == 'G'
      quantities_top_products = user.pointsale.sales_details.joins(:sale, :product).where("sales.status != 1 and sales.date_sale between ? and ?", beg_period, end_period).group('products.name').order('sum_quantity desc').limit(10).sum(:quantity)
      total_top_products = user.pointsale.sales_details.joins(:sale).where('sales.status != 1 and sales.date_sale between ? and ?', beg_period, end_period).order('sum_quantity desc').limit(10).sum(:quantity)
      total_sold = user.pointsale.sales_details.joins(:sale).where('sales.status != 1 and sales.date_sale between ? and ?', beg_period, end_period).sum(:quantity)
    end

    others = total_sold - total_top_products
    if others > 0
      quantities_top_products[:Otros] = others
    end
    quantities_top_products
  end

  def get_available_children(just_pointsales)
    children = Product.where("parent_id = ? and status != 0 ", id).pluck(:id)
    if just_pointsales
      AvailableProduct.where("product_id IN (?)", children).joins(:pointsale).select(:pointsale_id, :name).distinct(:pointsale_id)
    else
      AvailableProduct.where("product_id IN (?)", children)
    end
  end
end
