# frozen_string_literal: true

require 'travis/cli'
require 'travis/tools/ssl_key'
require 'travis/tools/github'

module Travis
  module CLI
    class Sshkey < RepoCommand
      description 'checks, updates or deletes an SSH key'
      on '-D', '--delete',                  'remove SSH key'
      on '-d', '--description DESCRIPTION', 'set description'
      on '-u', '--upload FILE',             'upload key from given file'
      on '-s', '--stdin',                   'upload key read from stdin'
      on '-c', '--check',                   'set exit code depending on key existing'
      on '-g', '--generate',                'generate SSH key and set up for given GitHub user'
      on '-p', '--passphrase PASSPHRASE',   'pass phrase to decrypt with when using --upload'
      on '-g', '--github-token TOKEN',      'identify by GitHub token'

      def_delegators :repository, :ssh_key

      def run
        delete_key                            if delete?
        update_key File.read(upload), upload  if upload?
        update_key $stdin.read, 'stdin'       if stdin?
        generate_key                          if generate?
        display_key
      end

      def display_key
        say "Current SSH key: #{color(ssh_key.description, :info)}"
        say "Finger print:    #{color(ssh_key.fingerprint, :info)}"
      rescue Travis::Client::NotFound
        say 'No custom SSH key installed.'
        exit 1 if check?
      end

      def update_key(value, file)
        error "#{file} does not look like a private key" unless value.lines.first =~ /PRIVATE KEY/
        value = remove_passphrase(value)
        self.description ||= ask('Key description: ') { |q| q.default = 'Custom Key' } if interactive?
        say "Updating ssh key for #{color slug, :info} with key from #{color file, :info}"
        empty_line
        ssh_key.update(value:, description: description || file)
      end

      def delete_key
        return if interactive? && !danger_zone?("Remove SSH key for #{color slug, :info}?")

        say "Removing ssh key for #{color slug, :info}"
        ssh_key.delete
      rescue Travis::Client::NotFound
        warn 'no key found to remove'
      end

      def generate_key
        access_token = nil
        github.with_token do |token|
          access_token = github_auth(token)
        end
        raise Travis::Client::GitHubLoginFailed, 'all GitHub tokens given were invalid' unless access_token

        gh = GH.with(token: github_token)
        login = gh['user']['login']
        check_access(gh)
        empty_line

        say 'Generating RSA key.'
        private_key        = Tools::SSLKey.generate_rsa
        self.description ||= "key for fetching dependencies for #{slug} via #{login}"

        say 'Uploading public key to GitHub.'
        gh.post('/user/keys', title: "#{description} (Travis CI)",
                              key: Tools::SSLKey.rsa_ssh(private_key.public_key))

        say 'Uploading private key to Travis CI.'
        ssh_key.update(value: private_key.to_s, description:)

        empty_line
        say 'You can store the private key to reuse it for other repositories (travis sshkey --upload FILE).'
        return unless agree('Store private key? ') { |q| q.default = 'no' }

        path = ask('Path: ') { |q| q.default = 'id_travis_rsa' }
        File.write(path, private_key.to_s)
      end

      def remove_passphrase(value)
        return value unless Tools::SSLKey.has_passphrase? value
        return Tools::SSLKey.remove_passphrase(value, passphrase) || error('wrong pass phrase') if passphrase

        error 'Key is encrypted, but missing --passphrase option' unless interactive?
        say 'The private key is protected by a pass phrase.'
        result = Tools::SSLKey.remove_passphrase(value, ask('Enter pass phrase: ') { |q| q.echo = '*' }) until result
        empty_line
        result
      end

      def check_access(gh)
        gh["repos/#{slug}"]
      rescue GH::Error
        error "GitHub account has no read access to #{color slug, :bold}"
      end

      def github
        @github ||= begin
          load_gh
          Tools::Github.new(session.config['github']) do |g|
            g.note          = "token for fetching dependencies for #{slug} (Travis CI)"
            g.explode       = explode?
            g.github_token  = github_token
            g.login_header  = proc { login_header }
            g.debug         = proc { |log| debug(log) }
            g.after_tokens  = proc { g.explode = true and error('no suitable github token found') }
          end
        end
      end

      def login_header
        say 'GitHub deprecated its Authorizations API exchanging a password for a token.'
        say 'Please visit https://github.blog/2020-07-30-token-authentication-requirements-for-api-and-git-operations for more information.'
        say "Try running with #{color('--github-token', :info)} or #{color('--auto-token', :info)} ."
      end
    end
  end
end
