# frozen_string_literal: true

module UserAssignments
  class PropagateAssignmentJob < ApplicationJob
    include Canaid::Helpers::PermissionsHelper

    queue_as :high_priority

    def perform(assignment, destroy: false, remove_from_team: false)
      @assignment = assignment
      @resource = assignment.assignable
      @destroy = destroy
      @remove_from_team = remove_from_team
      @type = assignment.class.model_name.param_key.gsub('_assignment', '').to_sym

      ActiveRecord::Base.transaction do
        @assignment.destroy! if destroy && !@assignment.destroyed?
        cleanup!(@assignment)
        sync_resource_user_associations(@resource)
      end
    end

    private

    def sync_resource_user_associations(resource)
      # stop role update propagation for child resources when encountering a manual assignment
      return if resource != @resource && # child resource
                !@destroy &&
                resource.public_send(:"#{@type}_assignments").find_by(
                  "#{@type}_id" => @assignment.public_send(@type).id
                ).manually_assigned?

      child_associations =
        case resource
        when Project
          resource.experiments
        when Experiment
          resource.my_modules
        else
          return
        end

      child_associations.find_each do |child_association|
        if @destroy
          destroy_assignment(child_association)
        else
          create_or_update_assignment(child_association)
        end

        sync_resource_user_associations(child_association)
      end

      destroy_assignment(resource) if resource.is_a?(Project) && @destroy
    end

    def create_or_update_assignment(resource)
      assignment =
        @assignment.class.find_or_initialize_by(
          "#{@type}_id": @assignment.public_send(@type).id,
          assignable: resource
        )

      return if assignment.manually_assigned?

      assignment.update!(
        user_role: @assignment.user_role,
        assigned_by: @assignment.assigned_by,
        assigned: :automatically
      )
    end

    def destroy_assignment(resource)
      assignment = resource.public_send(:"#{@type}_assignments").find_by(
        "#{@type}_id" => @assignment.public_send(@type).id
      )

      return unless assignment

      assignment.destroy!

      cleanup!(assignment)
    end

    def cleanup!(assignment)
      # clean up designations and favorites if user is no longer assigned
      assigned_users = assignment.assignable.users

      assignment.assignable.favorites.where.not(user: assigned_users).destroy_all if assignment.assignable.respond_to?(:favorites)
      assignment.assignable.user_my_modules.where.not(user: assigned_users).destroy_all if assignment.assignable.is_a?(MyModule)
    end
  end
end
