class BatchOperation < ApplicationRecord
  enum operation: {
    accepter: 'accepter',
    refuser: 'refuser',
    classer_sans_suite: 'classer_sans_suite',
    archiver: 'archiver',
    desarchiver: 'desarchiver',
    follow: 'follow',
    passer_en_instruction: 'passer_en_instruction',
    repousser_expiration: 'repousser_expiration',
    repasser_en_construction: 'repasser_en_construction',
    restaurer: 'restaurer',
    unfollow: 'unfollow',
    supprimer: 'supprimer'
  }

  has_many :dossiers, dependent: :nullify
  has_many :dossier_operations, class_name: 'DossierBatchOperation', dependent: :destroy
  has_many :groupe_instructeurs, through: :dossier_operations
  belongs_to :instructeur

  store_accessor :payload, :motivation, :justificatif_motivation

  validates :operation, presence: true

  before_create :build_operations

  RETENTION_DURATION = 4.hours
  MAX_DUREE_GENERATION = 24.hours

  scope :stale, lambda {
    where.not(finished_at: nil)
      .where('updated_at < ?', (Time.zone.now - RETENTION_DURATION))
  }

  scope :stuck, lambda {
    where(finished_at: nil)
      .where('updated_at < ?', (Time.zone.now - MAX_DUREE_GENERATION))
  }

  def dossiers_safe_scope(dossier_ids = self.dossier_ids)
    query = instructeur
      .dossiers
      .where(id: dossier_ids)
    case operation
    when BatchOperation.operations.fetch(:archiver) then
      query.visible_by_administration.not_archived.state_termine
    when BatchOperation.operations.fetch(:desarchiver) then
      query.visible_by_administration.archived.state_termine
    when BatchOperation.operations.fetch(:passer_en_instruction) then
      query.visible_by_administration.state_en_construction
    when BatchOperation.operations.fetch(:accepter) then
      query.visible_by_administration.state_en_instruction
    when BatchOperation.operations.fetch(:refuser) then
      query.visible_by_administration.state_en_instruction
    when BatchOperation.operations.fetch(:classer_sans_suite) then
      query.visible_by_administration.state_en_instruction
    when BatchOperation.operations.fetch(:follow) then
      query.visible_by_administration.without_followers.en_cours
    when BatchOperation.operations.fetch(:repousser_expiration) then
      query.visible_by_administration.termine_or_en_construction_close_to_expiration
    when BatchOperation.operations.fetch(:repasser_en_construction) then
      query.visible_by_administration.state_en_instruction
    when BatchOperation.operations.fetch(:unfollow) then
      query.visible_by_administration.with_followers.en_cours
    when BatchOperation.operations.fetch(:supprimer) then
      query.visible_by_administration.state_termine
    when BatchOperation.operations.fetch(:restaurer) then
      query.hidden_by_administration
    end
  end

  def enqueue_all
    dossiers_safe_scope # later in batch .
      .map { |dossier| BatchOperationProcessOneJob.perform_later(self, dossier) }
  end

  def process_one(dossier)
    case operation
    when BatchOperation.operations.fetch(:archiver)
      dossier.archiver!(instructeur)
    when BatchOperation.operations.fetch(:desarchiver)
      dossier.desarchiver!
    when BatchOperation.operations.fetch(:passer_en_instruction)
      dossier.passer_en_instruction!(instructeur: instructeur)
    when BatchOperation.operations.fetch(:accepter)
      dossier.accepter!(instructeur: instructeur, motivation: motivation, justificatif: justificatif_motivation)
    when BatchOperation.operations.fetch(:refuser)
      dossier.refuser!(instructeur: instructeur, motivation: motivation, justificatif: justificatif_motivation)
    when BatchOperation.operations.fetch(:classer_sans_suite)
      dossier.classer_sans_suite!(instructeur: instructeur, motivation: motivation, justificatif: justificatif_motivation)
    when BatchOperation.operations.fetch(:follow)
      instructeur.follow(dossier)
    when BatchOperation.operations.fetch(:repousser_expiration)
      dossier.extend_conservation(1.month)
    when BatchOperation.operations.fetch(:repasser_en_construction)
      dossier.repasser_en_construction!(instructeur: instructeur)
    when BatchOperation.operations.fetch(:unfollow)
      instructeur.unfollow(dossier)
    when BatchOperation.operations.fetch(:supprimer)
      dossier.hide_and_keep_track!(instructeur, :instructeur_request)
    when BatchOperation.operations.fetch(:restaurer)
      dossier.restore(instructeur)
    end
  end

  def track_processed_dossier(success, dossier)
    dossiers.delete(dossier)
    touch(:run_at) if called_for_first_time?
    touch(:finished_at)

    if success
      dossier_operation(dossier).done!
    else
      dossier_operation(dossier).fail!
    end
  end

  # when an instructeur want to create a batch from his interface,
  #   another one might have run something on one of the dossier
  #   we use this approach to create a batch with given dossiers safely
  def self.safe_create!(params)
    transaction do
      instance = new(params)
      instance.dossiers = instance.dossiers_safe_scope(params[:dossier_ids])
        .not_having_batch_operation
      if instance.dossiers.present?
        instance.save!
        BatchOperationEnqueueAllJob.perform_later(instance)
        instance
      end
    end
  end

  def called_for_first_time?
    run_at.nil?
  end

  def total_count
    dossier_operations.size
  end

  def success_count
    dossier_operations.success.size
  end

  def errors?
    dossier_operations.error.present?
  end

  def finished_at
    dossiers.empty? ? super : nil
  end

  private

  def dossier_operation(dossier)
    dossier_operations.find_by!(dossier:)
  end

  def build_operations
    dossier_operations.build(dossiers.map { { dossier: _1 } })
  end
end