class Staging::Workflow < ApplicationRecord
  def self.table_name_prefix
    'staging_'
  end

  include CanRenderModel

  belongs_to :project, inverse_of: :staging
  belongs_to :managers_group, class_name: 'Group'

  has_many :staging_projects, class_name: 'Project', inverse_of: :staging_workflow, dependent: :nullify,
                              foreign_key: 'staging_workflow_id' do
    def without_staged_requests
      left_outer_joins(:staged_requests).where(bs_requests: { id: nil })
    end
  end

  has_many :target_of_bs_requests, through: :project, foreign_key: 'staging_workflow_id' do
    def stageable(managers_group_title = nil)
      managers_group_title ||= proxy_association.owner.managers_group.try(:title)
      includes(:reviews).where(state: :review, staging_project_id: nil, reviews: { state: :new, by_group: managers_group_title })
    end
  end

  has_many :staged_requests, class_name: 'BsRequest', through: :staging_projects
  has_many :request_exclusions, class_name: 'Staging::RequestExclusion', foreign_key: 'staging_workflow_id', dependent: :destroy
  has_many :excluded_requests, through: :request_exclusions, source: :bs_request

  after_create :create_staging_projects
  after_create :add_reviewer_group
  before_update :update_managers_group

  attr_accessor :commit_user

  after_initialize do
    @commit_user = User.session
  end

  def unassigned_requests
    target_of_bs_requests.stageable.where.not(id: excluded_requests)
  end

  def ready_requests
    target_of_bs_requests.where(state: :new).where.not(id: excluded_requests)
  end

  def write_to_backend
    raise ArgumentError, 'no commit user set' unless commit_user
    return unless CONFIG['global_write_through']

    Backend::Api::Sources::Project.write_staging_workflow(project.name, commit_user.login, render_xml)
  end

  def self.load_groups
    # as it is not expected that there are many groups (~30) we cache all of them. Otherwise use this instead:
    # group_ids = Review.where(bs_request: BsRequest.where(staging_project: @staging_projects)).select('group_id').distinct
    # Group.where(id: group_ids).each do |group|
    #
    # TODO: Refactor this code using to_h when updating to Ruby 2.6 (performance improvement)
    Rails.cache.fetch("groups_hash_#{Group.all.cache_key_with_version}") do
      groups_hash = {}
      Group.find_each do |group|
        groups_hash[group.title] = group
      end
      groups_hash
    end
  end

  def self.load_users(staging_projects)
    # TODO: Refactor this code using to_h when updating to Ruby 2.6 (performance improvement)
    users_hash = {}
    user_ids = Review.where(bs_request: BsRequest.where(staging_project: staging_projects)).select('user_id').distinct
    User.where(id: user_ids).find_each do |user|
      users_hash[user.login] = user
    end
    users_hash
  end

  def autocomplete(num)
    unassigned_requests.where('CAST(bs_requests.number AS CHAR) LIKE ?', "%#{num}%")
  end

  private

  def create_staging_projects
    raise ArgumentError, 'no commit user set' unless commit_user

    %w[A B].each do |letter|
      staging_project = Project.find_or_initialize_by(name: "#{project.name}:Staging:#{letter}")
      next if staging_project.staging_workflow # if it belongs to another staging workflow skip it

      staging_project.staging_workflow = self
      staging_project.commit_user = commit_user
      staging_project.store
    end
  end

  def add_reviewer_group
    role = Role.find_by_title('reviewer')
    project.relationships.find_or_create_by(group: managers_group, role: role)
    project.commit_user = commit_user
    project.store
  end

  def update_managers_group
    return unless changes[:managers_group_id]

    old_managers_group = Group.find(changes[:managers_group_id].first)
    new_managers_group = managers_group

    # update reviewer group in backlog requests
    target_of_bs_requests.stageable(old_managers_group.title).each do |bs_request|
      bs_request.addreview(by_group: new_managers_group.title, comment: 'Staging manager group changed')
      bs_request.change_review_state(:accepted, by_group: old_managers_group.title, comment: 'Staging manager group changed')
    end

    # update managers group in staging projects
    staging_projects.each do |staging_project|
      staging_project.unassign_managers_group(old_managers_group)
      staging_project.assign_managers_group(new_managers_group)
      staging_project.store
    end

    # FIXME: This assignation is need because after store a staging_project
    # the object is reloaded and we lost the changes.
    self.managers_group = new_managers_group

    reviewer_role = Role.find_by_title('reviewer')
    project.relationships.find_by(group: old_managers_group, role: reviewer_role)&.destroy   # Remove reviewer role for old managers group
    project.relationships.find_or_create_by!(group: new_managers_group, role: reviewer_role) # Add reviewer role for new managers group

    project.store
  end
end
# == Schema Information
#
# Table name: staging_workflows
#
#  id                :integer          not null, primary key
#  created_at        :datetime         not null
#  updated_at        :datetime         not null
#  managers_group_id :integer          indexed
#  project_id        :integer          uniquely indexed
#
# Indexes
#
#  index_staging_workflows_on_managers_group_id  (managers_group_id)
#  index_staging_workflows_on_project_id         (project_id) UNIQUE
#
