# frozen_string_literal: true

# Project is the documentation about an object built by a fab-user
# It can describe the steps taken by the fab-user to build his object, provide photos, description, attached CAO files, etc.
class Project < ApplicationRecord
  include AASM
  include NotifyWith::NotificationAttachedObject
  include OpenlabSync

  # elastic initialisations
  include Elasticsearch::Model
  index_name 'fablab'
  document_type 'projects'

  # kaminari
  # -- dependency in app/assets/javascripts/controllers/projects.js.erb
  paginates_per 16

  # friendlyId
  extend FriendlyId
  friendly_id :name, use: :slugged

  has_one :project_image, as: :viewable, dependent: :destroy
  accepts_nested_attributes_for :project_image, allow_destroy: true
  has_many :project_caos, as: :viewable, dependent: :destroy
  accepts_nested_attributes_for :project_caos, allow_destroy: true, reject_if: :all_blank

  has_and_belongs_to_many :machines, join_table: 'projects_machines'
  has_and_belongs_to_many :spaces, join_table: 'projects_spaces'
  has_and_belongs_to_many :components, join_table: 'projects_components'
  has_and_belongs_to_many :themes, join_table: 'projects_themes'

  has_many :project_users, dependent: :destroy
  has_many :users, through: :project_users

  belongs_to :author, foreign_key: :author_statistic_profile_id, class_name: 'StatisticProfile'
  belongs_to :licence, foreign_key: :licence_id

  has_many :project_steps, dependent: :destroy
  accepts_nested_attributes_for :project_steps, allow_destroy: true

  # validations
  validates :author, :name, presence: true

  after_save :after_save_and_publish

  aasm column: 'state' do
    state :draft, initial: true
    state :published

    event :publish, after: :notify_admin_when_project_published do
      transitions from: :draft, to: :published
    end
  end

  # scopes
  scope :published, -> { where("state = 'published'") }

  ## elastic
  # callbacks
  after_save { ProjectIndexerWorker.perform_async(:index, id) }
  after_destroy { ProjectIndexerWorker.perform_async(:delete, id) }

  # mapping
  settings index: { number_of_replicas: 0 } do
    mappings dynamic: 'true' do
      indexes 'state', analyzer: 'simple'
      indexes 'tags', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
      indexes 'name', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
      indexes 'description', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
      indexes 'project_steps' do
        indexes 'title', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
        indexes 'description', analyzer: Rails.application.secrets.elasticsearch_language_analyzer
      end
    end
  end

  # the resulting JSON will be indexed in ElasticSearch, as /fablab/projects
  def as_indexed_json
    ApplicationController.new.view_context.render(
      partial: 'api/projects/indexed',
      locals: { project: self },
      formats: [:json],
      handlers: [:jbuilder]
    )
  end

  def self.search(params, current_user)
    Project.__elasticsearch__.search(build_search_query_from_context(params, current_user))
  end

  def self.build_search_query_from_context(params, current_user)
    search = {
      query: {
        bool: {
          must: [],
          should: [],
          filter: []
        }
      }
    }

    # we sort by created_at if there isn't a query
    if params['q'].blank?
      search[:sort] = { created_at: { order: :desc } }
    else
      # otherwise we search for the word (q) in various fields
      search[:query][:bool][:must] << {
        multi_match: {
          query: params['q'],
          type: 'most_fields',
          fields: %w[tags^4 name^5 description^3 project_steps.title^2 project_steps.description]
        }
      }
    end

    # we filter by themes, components, machines
    params.each do |name, value|
      if name =~ /(.+_id$)/
        search[:query][:bool][:filter] << { term: { "#{name}s" => value } } if value
      end
    end

    # if use select filter 'my project' or 'my collaborations'
    if current_user && params.key?('from')
      search[:query][:bool][:filter] << { term: { author_id: current_user.id } } if params['from'] == 'mine'
      search[:query][:bool][:filter] << { term: { user_ids: current_user.id } } if params['from'] == 'collaboration'
    end

    # if user is connected, also display his draft projects
    if current_user
      search[:query][:bool][:should] << { term: { state: 'published' } }
      search[:query][:bool][:should] << { term: { author_id: current_user.id } }
      search[:query][:bool][:should] << { term: { user_ids: current_user.id } }
    else
      # otherwise display only published projects
      search[:query][:bool][:must] << { term: { state: 'published' } }
    end

    search
  end

  private

  def notify_admin_when_project_published
    NotificationCenter.call type: 'notify_admin_when_project_published',
                            receiver: User.admins_and_managers,
                            attached_object: self
  end

  def after_save_and_publish
    return unless saved_change_to_state? && published?

    update_columns(published_at: DateTime.current)
    notify_admin_when_project_published
  end
end