#!/usr/bin/env ruby

# frozen_string_literal: true

require 'optparse'
require 'gitlab'

module Packages
  class AutomatedCleanup
    PACKAGES_PER_PAGE = 100

    # $GITLAB_PROJECT_PACKAGES_CLEANUP_API_TOKEN => `Packages Cleanup` project token
    def initialize(
      project_path: ENV['CI_PROJECT_PATH'],
      gitlab_token: ENV['GITLAB_PROJECT_PACKAGES_CLEANUP_API_TOKEN'],
      api_endpoint: ENV['CI_API_V4_URL'],
      options: {}
    )
      @project_path = project_path
      @gitlab_token = gitlab_token
      @api_endpoint = api_endpoint
      @dry_run = options[:dry_run]

      puts "Dry-run mode." if dry_run
    end

    def gitlab
      @gitlab ||= begin
        Gitlab.configure do |config|
          config.endpoint = api_endpoint
          config.private_token = gitlab_token
        end

        Gitlab
      end
    end

    def perform_gitlab_package_cleanup!(package_name:, days_for_delete:)
      puts "Checking for '#{package_name}' packages created at least #{days_for_delete} days ago..."

      gitlab.project_packages(project_path,
                              package_type: 'generic',
                              package_name: package_name,
                              per_page: PACKAGES_PER_PAGE).auto_paginate do |package|
        next unless package.name == package_name # the search is fuzzy, so we better check the actual package name

        if old_enough(package, days_for_delete) && not_recently_downloaded(package, days_for_delete)
          delete_package(package)
        end
      end
    end

    private

    attr_reader :project_path, :gitlab_token, :api_endpoint, :dry_run

    def delete_package(package)
      print_package_state(package)
      gitlab.delete_project_package(project_path, package.id) unless dry_run
    rescue Gitlab::Error::Forbidden
      puts "Package #{package_full_name(package)} is forbidden: skipping it"
    end

    def time_ago(days:)
      Time.now - days * 24 * 3600
    end

    def old_enough(package, days_for_delete)
      Time.parse(package.created_at) < time_ago(days: days_for_delete)
    end

    def not_recently_downloaded(package, days_for_delete)
      package.last_downloaded_at.nil? ||
        Time.parse(package.last_downloaded_at) < time_ago(days: days_for_delete)
    end

    def print_package_state(package)
      download_text =
        if package.last_downloaded_at
          "last downloaded on #{package.last_downloaded_at}"
        else
          "never downloaded"
        end

      puts "\nPackage #{package_full_name(package)} (created on #{package.created_at}) was " \
        "#{download_text}: deleting it.\n"
    end

    def package_full_name(package)
      "'#{package.name}/#{package.version}'"
    end
  end
end

def timed(task)
  start = Time.now
  yield(self)
  puts "#{task} finished in #{Time.now - start} seconds.\n"
end

if $PROGRAM_NAME == __FILE__
  options = {
    dry_run: false
  }

  OptionParser.new do |opts|
    opts.on("-d", "--dry-run", "Whether to perform a dry-run or not.") do |value|
      options[:dry_run] = true
    end

    opts.on("-h", "--help", "Prints this help") do
      puts opts
      exit
    end
  end.parse!

  automated_cleanup = Packages::AutomatedCleanup.new(options: options)

  timed('"gitlab-workhorse" packages cleanup') do
    automated_cleanup.perform_gitlab_package_cleanup!(package_name: 'gitlab-workhorse', days_for_delete: 30)
  end

  timed('"assets" packages cleanup') do
    automated_cleanup.perform_gitlab_package_cleanup!(package_name: 'assets', days_for_delete: 7)
  end

  timed('"fixtures" packages cleanup') do
    automated_cleanup.perform_gitlab_package_cleanup!(package_name: 'fixtures', days_for_delete: 14)
  end
end