require 'fileutils'
require 'optparse'

require_relative './migrate'
require_relative './import'

module GitlabCtl
  module Registry
    module Database
      EXEC_PATH = '/opt/gitlab/embedded/bin/registry'.freeze
      CONFIG_PATH = '/var/opt/gitlab/registry/config.yml'.freeze

      USAGE = <<~EOS.freeze
        Usage:
          gitlab-ctl registry-database command subcommand [options]

        GLOBAL OPTIONS:
          -h, --help      Usage help

        COMMANDS:
          migrate                Manage schema migrations
          import                 Import filesystem metadata into the database
      EOS

      def self.parse_options!(ctl, args)
        @ctl = ctl

        loop do
          break if args.shift == 'registry-database'
        end

        global = OptionParser.new do |opts|
          opts.on('-h', '--help', 'Usage help') do
            Kernel.puts USAGE
            Kernel.exit 0
          end
        end

        global.order!(args)

        # the command is needed by the dependencies in populate_commands
        command = args[0]
        raise OptionParser::ParseError, "registry-database command is not specified." \
          if command.nil? || command.empty?

        options = {}
        commands = populate_commands(options)

        raise OptionParser::ParseError, "Unknown registry-database command: #{command}" \
          unless commands.key?(command)

        commands[command].parse!(args)
        options[:command] = command

        options
      end

      def self.populate_commands(options)
        database_docs_url = 'https://gitlab.com/gitlab-org/container-registry/-/blob/master/docs/database-migrations.md#administration'

        {
          'migrate' => OptionParser.new do |opts|
            opts.banner = "Usage: gitlab-ctl registry-database migrate SUBCOMMAND [options]. See documentation at #{database_docs_url}"
            begin
              GitlabCtl::Registry::Migrate.parse_options!(ARGV, options)
            rescue OptionParser::ParseError => e
              warn "#{e}\n\n#{GitlabCtl::Registry::Migrate::USAGE}"
              exit 128
            end
          end,

          'import' => OptionParser.new do |parser|
            GitlabCtl::Registry::Import.parse_options!(ARGV, parser, options)
          rescue OptionParser::ParseError => e
            warn "#{e}\n\n#{GitlabCtl::Registry::Import::USAGE}"
            exit 128
          end,
        }
      end

      def self.usage
        USAGE
      end

      def self.execute(options)
        unless enabled?
          log "Container registry is not enabled, exiting..."
          return
        end

        [EXEC_PATH, CONFIG_PATH].each do |path|
          next if File.exist?(path)

          Kernel.abort "Could not find '#{path}' file. Is this command being run on a Container Registry node?"
        end

        command = set_command(options)

        puts "Executing command:\n#{command.join(' ')}\n"

        begin
          status = Kernel.system(*command)
          Kernel.exit!(1) unless status
        ensure
          start! unless running?
        end
      end

      def self.set_command(options)
        command = [EXEC_PATH, "database", options[:command]]
        options.delete(:command)

        command += [options[:subcommand]] unless options[:subcommand].nil?
        options.delete(:subcommand)

        needs_stop = options[:needs_stop]
        options.delete(:needs_stop)

        needs_read_only = options[:needs_read_only]
        options.delete(:needs_read_only)

        continue?(needs_stop, needs_read_only)

        command += ["-n", options[:limit]] unless options[:limit].nil?
        options.delete(:limit)

        options.each do |_, opt|
          command.append(opt)
        end

        # always set the config file at the end
        command += [CONFIG_PATH]

        command
      end

      def self.log(msg)
        @ctl.log(msg)
      end

      def self.running?
        !GitlabCtl::Util.run_command("gitlab-ctl status #{service_name}").error?
      end

      def self.read_only?
        config = YAML.load_file(CONFIG_PATH)
        config.dig('storage', 'maintenance', 'readonly', 'enabled') == true
      end

      def self.start!
        puts "Starting service #{service_name}"

        @ctl.run_sv_command_for_service('start', service_name)
      end

      def self.stop!
        puts "Stopping service #{service_name}"

        @ctl.run_sv_command_for_service('stop', service_name)
      end

      def self.enabled?
        @ctl.service_enabled?(service_name)
      end

      def self.config?
        File.exist?(@path)
      end

      def self.service_name
        "registry"
      end

      def self.continue?(needs_stop, needs_read_only)
        if needs_stop && running?
          puts 'WARNING: Command cannot run while the container registry is running. ' \
              'Stop the registry before proceeding? (y/n)'.color(:yellow)

          if $stdin.gets.chomp.casecmp('y').zero?
            stop!
          else
            puts "Exiting..."
            exit 1
          end
        end

        return unless running? && needs_read_only && !read_only?

        puts 'Command requires the container registry to be in read-only mode. Exiting...'
        exit 1
      end
    end
  end
end
