# frozen_string_literal: true

# Benchmark script for Group#user_added performance.
#
# Measures SQL query count and elapsed time when adding a user to a group
# that has memberships in multiple projects with inheriting subprojects.
#
# This script has no dependency on its file location.
# It can be run from anywhere:
#
#   bin/rails runner /path/to/benchmark_group_user_added.rb
#
# Configuration:
#   Adjust NUM_PROJECTS, NUM_ROLES, SUBPROJECT_DEPTH below.

require 'benchmark'

NUM_PROJECTS     = 50
NUM_ROLES        = 3
SUBPROJECT_DEPTH = 5

puts "=== Group#user_added benchmark ==="
puts "Projects: #{NUM_PROJECTS}, Roles: #{NUM_ROLES}, Subproject depth: #{SUBPROJECT_DEPTH}"
puts

seq = SecureRandom.hex(4)

# -- Setup --
group = Group.create!(:lastname => "BenchGroup_#{seq}")

user = User.new(
  :login => "benchuser_#{seq}",
  :firstname => 'Bench',
  :lastname => 'User',
  :mail => "benchuser_#{seq}@example.com"
)
user.password = 'password'
user.save!

roles = Role.where(:builtin => 0).limit(NUM_ROLES).to_a
raise "Need at least #{NUM_ROLES} non-builtin roles" if roles.size < NUM_ROLES

role_ids = roles.map(&:id)

projects = NUM_PROJECTS.times.map do |i|
  root = Project.create!(
    :name => "bench_#{seq}_#{i}",
    :identifier => "bench-#{seq}-#{i}",
    :is_public => false
  )
  Member.create!(:principal => group, :project => root, :role_ids => role_ids)

  parent = root
  SUBPROJECT_DEPTH.times do |d|
    child = Project.new(
      :name => "bench_#{seq}_#{i}_d#{d}",
      :identifier => "bench-#{seq}-#{i}-d#{d}",
      :is_public => false,
      :inherit_members => true
    )
    child.parent = parent
    child.save!
    parent = child
  end
  root
end

total_projects = NUM_PROJECTS * (1 + SUBPROJECT_DEPTH)
puts "Setup complete: #{NUM_PROJECTS} root projects, each with #{SUBPROJECT_DEPTH} levels of inheriting subprojects"
puts "Total projects: #{total_projects}"
puts

# -- Count queries --
query_count = 0
query_details = Hash.new(0)

subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*, payload|
  unless payload[:name] == 'SCHEMA' || payload[:sql].start_with?('PRAGMA')
    query_count += 1
    op = payload[:sql].split(' ', 2).first.upcase
    query_details[op] += 1
  end
end

elapsed = Benchmark.realtime do
  group.users << user
end

ActiveSupport::Notifications.unsubscribe(subscriber)

puts "--- Results ---"
puts "Time:    #{(elapsed * 1000).round(1)} ms"
puts "Queries: #{query_count}"
puts
puts "Query breakdown:"
query_details.sort_by { |_, v| -v }.each do |op, count|
  puts "  #{op}: #{count}"
end
puts

# -- Verify correctness --
errors = []
projects.each do |root|
  root.reload
  Project.where("lft >= ? AND rgt <= ?", root.lft, root.rgt).each do |proj|
    unless user.member_of?(proj)
      errors << "User NOT a member of project #{proj.id} (#{proj.identifier})"
    end
  end
end

if errors.empty?
  puts "Correctness: OK (user is a member of all #{total_projects} projects)"
else
  puts "Correctness: FAILED"
  errors.first(10).each { |e| puts "  #{e}" }
  puts "  ... (#{errors.size} total)" if errors.size > 10
end

# -- Cleanup --
projects.each do |root|
  root.reload
  Project.where("lft >= ? AND rgt <= ?", root.lft, root.rgt).order(lft: :desc).each(&:destroy)
end
group.destroy
user.destroy

puts
puts "Cleanup complete."
