# frozen_string_literal: true

require 'fileutils'
require 'erb'
require 'shellwords'
require 'etc'

module GDK
  module Command
    class Report < BaseCommand
      REPORT_TEMPLATE_PATH = 'lib/support/files/report_template.md.erb'
      LOG_NAMES = %w[
        gitlab-db-migrate
        update-make-ensure-databases-setup
        update-make-gitlab-topology-service-update
        reconfigure-make-gdk-reconfigure-task
        update-make-gitaly-update
        update-make-gitlab-translations
        reconfigure-make-gitlab-http-router-setup
        update-make-gitlab-asdf-install
        update-make-gitlab-workhorse-update
        reconfigure-make-gitlab-topology-service-setup
        update-make-gitlab-bundle
        update-make-gitlab-yarn
        reconfigure-make-postgresql
        update-make-gitlab-git
        update-make-postgresql
        update-gdk_bundle_install
        update-make-gitlab-http-router-update
        update-platform
        update-gitlab-git-pull
        update-make-gitlab-lefthook
        update-tool-versions
        update-graphql
        update-make-gitlab-shell-update
      ].freeze

      REPOSITORY_NAMES = %w[gdk gitaly gitlab].freeze
      NEW_ISSUE_URL = 'https://gitlab.com/gitlab-org/gitlab-development-kit/-/issues/new?issue[label]=~Category:GDK'
      LABELING = '/label ~type::bug ~bug::functional ~Category:GDK ~gdk-report ~group::developer tooling'
      COPY_COMMANDS = [
        'pbcopy', # macOS
        'xclip -selection clipboard', # Linux
        'xsel --clipboard --input', # Linux
        'wl-copy' # Wayland
      ].freeze

      OPEN_COMMANDS = [
        'open', # macOS
        'xdg-open' # Linux
      ].freeze

      def initialize
        @debug_info = GDK::Command::DebugInfo.new
      end

      def run(_ = [])
        @report_id = SecureRandom.uuid
        template_path = GDK.root.join(REPORT_TEMPLATE_PATH)
        GDK::Output.info('We are collecting report details, this might take a minute ...')

        # Create variables for the template
        report_json = {
          report_id: report_id,
          os_name: debug_info.os_name,
          arch: debug_info.arch,
          ruby_version: debug_info.ruby_version,
          gdk_version: debug_info.gdk_version,
          package_manager: package_manager,
          env_variables: env_variables,
          gdk_config: gdk_config,
          gdk_doctor: gdk_doctor,
          gem_env: gem_env,
          bundle_env: bundle_env,
          network_information: network_information,
          logs: logs,
          git_repositories: git_repositories,
          date_time: date_time
        }

        # Render the template
        renderer = Templates::ErbRenderer.new(template_path, report_json: report_json)
        report_content = renderer.render_to_string

        GDK::Output.puts report_content
        open_browser
        copy_clipboard(report_content)

        GDK::Output.info('This report has been copied to your clipboard.')
        GDK::Output.info('We opened the browser with a new issue, please paste this report from your clipboard into the description.')

        true
      end

      def package_manager
        if GDK.config.mise.enabled?
          "mise-en-place #{shellout('mise --version')}"
        elsif !GDK.config.asdf.opt_out?
          "asdf #{shellout('asdf version')}"
        else
          'Neither mise nor asdf is used.'
        end
      end

      def env_variables
        debug_info.env_vars.map do |var, content|
          "#{var}=#{content}"
        end.join("\n")
      end

      def gdk_config
        return 'No GDK configuration found.' unless debug_info.gdk_yml_exists?

        debug_info.gdk_yml
      end

      def gdk_doctor
        output = GDK::OutputBuffered.new
        GDK::Command::Doctor.new(out: output).run
        redact_home(output.dump.chomp)
      end

      def gem_env
        redact_home(shellout('gem env'))
      end

      def bundle_env
        redact_home(shellout('bundle env'))
      end

      def network_information
        shellout('lsof -iTCP -sTCP:LISTEN').gsub(Etc.getpwuid.name, '$USER')
      end

      def logs
        LOG_NAMES.each_with_object({}) do |service_name, logs|
          log_file_path = "log/gdk/#{log_file_name(service_name)}"
          next if Dir.glob(log_file_path).empty?

          logs[service_name] = redact_home(Support::Rake::TaskLogger.new(log_file_path).tail)
        end
      end

      def log_file_name(service_name)
        Time.now.strftime("rake-%Y-%m-%d_*/#{service_name}.log")
      end

      def git_repositories
        REPOSITORY_NAMES.each_with_object({}) do |repo_name, repositories|
          repositories[repo_name] = {
            git_status: git_status(repo_name),
            git_head: git_head(repo_name)
          }
        end
      end

      def date_time
        Time.now.strftime('%d/%m/%Y %H:%M:%S %Z')
      end

      def git_status(repo_name)
        command = repo_name == 'gdk' ? 'git status' : "cd #{repo_name} && git status"
        shellout(command)
      end

      def git_head(repo_name)
        command = repo_name == 'gdk' ? 'git show HEAD' : "cd #{repo_name} && git show HEAD"
        shellout(command)[/.*/]
      end

      def shellout(cmd, **args)
        debug_info.shellout(cmd, **args)
      end

      def redact_home(message)
        message.gsub(Dir.home, ConfigRedactor::HOME_REDACT_WITH)
      end

      def copy_clipboard(content)
        (command = find_command(COPY_COMMANDS)) ||
          abort('Could not automatically copy message to clipboard. Please copy the output manually.')

        IO.popen(::Shellwords.split(command), 'w') do |pipe|
          pipe.print(content)
        end
      end

      def open_browser
        (command = find_command(OPEN_COMMANDS)) ||
          abort('Could not automatically open browser. Please open the URL manually.')

        url = URI(NEW_ISSUE_URL)
        url.query = query

        system(*Shellwords.split(command), url)
      end

      def query
        URI.encode_www_form(
          'issue[issue_type]': :incident,
          'issue[confidential]': true,
          'issue[title]': "GDK Troubleshooting Report #{report_id}: ENTER A TITLE FOR YOUR REPORT",
          'issue[description]': description
        )
      end

      def description
        <<~MARKDOWN
          #{LABELING}

          <!-- Please paste the report from your clipboard below here. -->
        MARKDOWN
      end

      def find_command(list)
        list.find { |command| Utils.find_executable(command.split.first) }
      end

      private

      attr_reader :report_id, :debug_info
    end
  end
end
