require 'foodsoft_article_import'
class Supplier < ApplicationRecord
  include MarkAsDeletedWithName
  include CustomFields

  has_many :articles, -> { where(:type => nil).includes(:article_category).order('article_categories.name', 'articles.name') }
  has_many :stock_articles, -> { includes(:article_category).order('article_categories.name', 'articles.name') }
  has_many :orders
  has_many :deliveries
  has_many :invoices
  belongs_to :supplier_category
  belongs_to :shared_supplier, optional: true # for the sharedLists-App

  validates :name, :presence => true, :length => { :in => 4..30 }
  validates :phone, :presence => true, :length => { :in => 8..25 }
  validates :address, :presence => true, :length => { :in => 8..50 }
  validates_format_of :iban, :with => /\A[A-Z]{2}[0-9]{2}[0-9A-Z]{,30}\z/, :allow_blank => true
  validates_uniqueness_of :iban, :case_sensitive => false, :allow_blank => true
  validates_length_of :order_howto, :note, maximum: 250
  validate :valid_shared_sync_method
  validate :uniqueness_of_name

  scope :undeleted, -> { where(deleted_at: nil) }
  scope :having_articles, -> { where(id: Article.undeleted.select(:supplier_id).distinct) }

  def self.ransackable_attributes(auth_object = nil)
    %w(id name)
  end

  def self.ransackable_associations(auth_object = nil)
    %w(articles stock_articles orders)
  end

  # sync all articles with the external database
  # returns an array with articles(and prices), which should be updated (to use in a form)
  # also returns an array with outlisted_articles, which should be deleted
  # also returns an array with new articles, which should be added (depending on shared_sync_method)
  def sync_all
    updated_article_pairs, outlisted_articles, new_articles = [], [], []
    existing_articles = Set.new
    for article in articles.undeleted
      # try to find the associated shared_article
      shared_article = article.shared_article(self)

      if shared_article # article will be updated
        existing_articles.add(shared_article.id)
        unequal_attributes = article.shared_article_changed?(self)
        unless unequal_attributes.blank? # skip if shared_article has not been changed
          article.attributes = unequal_attributes
          updated_article_pairs << [article, unequal_attributes]
        end
      # Articles with no order number can be used to put non-shared articles
      # in a shared supplier, with sync keeping them.
      elsif not article.order_number.blank?
        # article isn't in external database anymore
        outlisted_articles << article
      end
    end
    # Find any new articles, unless the import is manual
    if ['all_available', 'all_unavailable'].include?(shared_sync_method)
      # build new articles
      shared_supplier
        .shared_articles
        .where.not(id: existing_articles.to_a)
        .find_each { |new_shared_article| new_articles << new_shared_article.build_new_article(self) }
      # make them unavailable when desired
      if shared_sync_method == 'all_unavailable'
        new_articles.each { |new_article| new_article.availability = false }
      end
    end
    return [updated_article_pairs, outlisted_articles, new_articles]
  end

  # Synchronise articles with spreadsheet.
  #
  # @param file [File] Spreadsheet file to parse
  # @param options [Hash] Options passed to {FoodsoftArticleImport#parse} except when listed here.
  # @option options [Boolean] :outlist_absent Set to +true+ to remove articles not in spreadsheet.
  # @option options [Boolean] :convert_units Omit or set to +true+ to keep current units, recomputing unit quantity and price.
  def sync_from_file(file, type, options = {})
    all_order_numbers = []
    updated_article_pairs, outlisted_articles, new_articles = [], [], []
    custom_codes_path = File.join(Rails.root, "config", "custom_codes.yml")
    opts = options.except(:convert_units, :outlist_absent)
    custom_codes_file_path = custom_codes_path if File.exist?(custom_codes_path)
    FoodsoftArticleImport.parse(file, custom_file_path: custom_codes_file_path, type: type, **opts) do |new_attrs, status, line|
      article = articles.undeleted.where(order_number: new_attrs[:order_number]).first
      new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category])
      new_attrs[:tax] ||= FoodsoftConfig[:tax_default]
      new_article = articles.build(new_attrs)

      if status.nil?
        if article.nil?
          new_articles << new_article
        else
          unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units))
          unless unequal_attributes.empty?
            article.attributes = unequal_attributes
            updated_article_pairs << [article, unequal_attributes]
          end
        end
      elsif status == :outlisted && article.present?
        outlisted_articles << article

      # stop when there is a parsing error
      elsif status.is_a? String
        # @todo move I18n key to model
        raise I18n.t('articles.model.error_parse', :msg => status, :line => line.to_s)
      end

      all_order_numbers << article.order_number if article
    end
    if options[:outlist_absent]
      outlisted_articles += articles.undeleted.where.not(order_number: all_order_numbers + [nil])
    end
    return [updated_article_pairs, outlisted_articles, new_articles]
  end

  # default value
  def shared_sync_method
    return unless shared_supplier

    self[:shared_sync_method] || 'import'
  end

  def deleted?
    deleted_at.present?
  end

  def mark_as_deleted
    transaction do
      super
      update_column :iban, nil
      articles.each(&:mark_as_deleted)
    end
  end

  # @return [Boolean] Whether there are articles that would use tolerance (unit_quantity > 1)
  def has_tolerance?
    articles.where('articles.unit_quantity > 1').any?
  end

  protected

  # make sure the shared_sync_method is allowed for the shared supplier
  def valid_shared_sync_method
    if shared_supplier && !shared_supplier.shared_sync_methods.include?(shared_sync_method)
      errors.add :shared_sync_method, :included
    end
  end

  # Make sure, the name is uniq, add usefull message if uniq group is already deleted
  def uniqueness_of_name
    supplier = Supplier.where(name: name)
    supplier = supplier.where.not(id: self.id) unless new_record?
    if supplier.exists?
      message = supplier.first.deleted? ? :taken_with_deleted : :taken
      errors.add :name, message
    end
  end
end