# frozen_string_literal: true

# SPDX-License-Identifier: GPL-3.0-only
# Copyright (C) 2026 NeatNerds BV <query@neatnerds.be>

require 'bundler/gem_tasks'
require 'rspec/core/rake_task'
require 'rubocop/rake_task'
require_relative 'lib/xuilian/version'

RSpec::Core::RakeTask.new(:spec)
RuboCop::RakeTask.new(:rubocop)

desc 'Run reek smell detector against the library tree'
task :reek do
  sh 'bundle', 'exec', 'reek', 'lib/'
end

desc 'Audit dependencies for known vulnerabilities (bundler-audit)'
task :bundler_audit do
  sh 'bundle', 'exec', 'bundler-audit', 'check', '--update'
end

desc 'Audit Ruby interpreter for known vulnerabilities (ruby_audit)'
task :ruby_audit do
  sh 'bundle', 'exec', 'ruby-audit', 'check'
end

desc 'Run YARD documentation coverage check (warns under 95%)'
task :yard_stats do
  require 'open3'
  output, _err, _status = Open3.capture3('bundle', 'exec', 'yard', 'stats', '--list-undoc')
  puts output
  coverage = output[/(\d+\.\d+)% documented/, 1].to_f
  if coverage < 95.0
    warn "YARD coverage #{coverage}% is below the 95% threshold"
    exit 1
  end
end

namespace :build do
  desc 'Build the .deb package via packaging/build.sh'
  task :package do
    # Detach from the caller's bundler context so the system-installed
    # fpm resolves outside our Gemfile (fpm is intentionally not bundled
    # — it pulls in libffi/libreadline build deps and CI installs it
    # separately via `gem install fpm --no-document`).
    require 'bundler'
    Bundler.with_unbundled_env { sh 'bash', 'packaging/build.sh' }
  end

  desc 'Build the gem via gem build'
  task :gem do
    sh 'gem', 'build', 'xuilian.gemspec'
  end
end

# Positive-allowlist of paths that are expected in the published gem.
# Rooted patterns are matched as fnmatch globs against each file's
# relative path inside the unpacked gem. Anything outside this list is
# flagged for review (sec_001 / sec_080 — denylist approaches miss
# session data, internal docs, business-sensitive content).
GEM_FILE_ALLOWLIST = [
  'bin/xuilian',
  'lib/xuilian.rb',
  'lib/xuilian/**/*.rb',
  'CHANGELOG.md',
  'README.md',
  'LICENSE'
].freeze

# Soft cap on gem size. xuilian is a tiny pure-Ruby gem; if it ever
# crosses 200 KB something is wrong (session transcripts, vendored gems,
# binary artifacts). sec_011 — flag a 20% delta against expected size.
GEM_SIZE_LIMIT_BYTES = 200 * 1024

def assert_gem_size(size)
  return if size <= GEM_SIZE_LIMIT_BYTES

  raise "gem size #{size} exceeds soft cap #{GEM_SIZE_LIMIT_BYTES} (sec_011)"
end

def assert_gem_allowlist(contents)
  unexpected = contents.reject do |path|
    GEM_FILE_ALLOWLIST.any? { |glob| File.fnmatch?(glob, path, File::FNM_PATHNAME) }
  end
  return if unexpected.empty?

  raise "gem contains files outside the positive allowlist (sec_001):\n  #{unexpected.join("\n  ")}"
end

desc 'Audit the built .gem file against the positive allowlist + size cap'
task :gem_audit do
  require 'rubygems/package'

  gem_files = FileList['xuilian-*.gem']
  raise 'no built .gem in CWD — run rake build:gem first' if gem_files.empty?

  gem_path = gem_files.max
  size = File.size(gem_path)
  puts "Gem: #{gem_path} (#{size} bytes)"
  assert_gem_size(size)

  contents = Gem::Package.new(gem_path).contents.sort
  contents.each { |path| puts "  #{path}" }
  assert_gem_allowlist(contents)

  puts "OK: #{contents.size} files, all within positive allowlist"
end

desc 'Run all quality gates (spec, rubocop, reek, security audits, yard coverage)'
task quality: %i[spec rubocop reek bundler_audit ruby_audit yard_stats]

task default: :quality

# ---------------------------------------------------------------------------
# Test, audit, build alias, and release-prep tasks (CI + overcommit parity)
# ---------------------------------------------------------------------------

desc 'Run the RSpec test suite'
task :test do
  sh 'bundle', 'exec', 'rspec'
end

desc 'Run all security audits (bundler-audit + ruby-audit)'
task audit: %i[bundler_audit ruby_audit]

desc 'Prepare a release: run audit + test, then print the release checklist'
task 'prepare-release' => %i[audit test] do
  puts "\n=== Release Readiness Check ==="
  puts "  Version: #{Xuilian::VERSION}"
  puts "\n  Next steps:"
  puts "    1. Review CHANGELOG.md — ensure v#{Xuilian::VERSION} is documented"
  puts "    2. git tag -a v#{Xuilian::VERSION} -m 'Release v#{Xuilian::VERSION}'"
  puts "    3. git push origin v#{Xuilian::VERSION}"
  puts '    4. rake build           # Build the gem (bundler standard, to pkg/)'
  puts '    5. rake build:package   # Build the .deb package'
end
