#!/usr/bin/env ruby

require 'fenton_shell'
require 'gli'

include GLI::App
include FentonShell

program_desc 'manage and sign SSH Keys from Fenton Server'

desc 'Show current version number'
version FentonShell::VERSION

desc 'Be verbose'
switch 'verbose'

sort_help :manually

pre_config = begin
               YAML.load_file("#{Dir.home}/.fenton/config")
             rescue
               {}
             end
private_key_default = begin
                        pre_config[:public_key].gsub('.pub', '')
                      rescue
                        "#{Dir.home}/.ssh/id_rsa"
                      end
public_key_default = "#{private_key_default}.pub"

default_fenton_directory = "#{Dir.home}/.fenton"

flag [:z, :fenton_server_url],
     desc: 'Fenton Server URL',
     default_value: pre_config[:fenton_server_url] || 'http://localhost:9292'
flag [:d, :directory], desc: 'Fenton Directory Path',
                       default_value: default_fenton_directory

desc 'Client Management'
arg_name 'client'

command :client do |c|
  c.default_desc 'Manage Clients'

  c.flag [:name], desc: 'Name'
  c.flag [:email], desc: 'Email'
  c.flag [:password], desc: 'Password'
  c.flag [:public_key], desc: 'SSH Public Key',
                        default_value: public_key_default

  @config = ConfigFile.new
  @client = Client.new

  c.desc 'Create an account (<username> --name --email --password --public_key)'
  arg_name 'signup'

  c.command :signup do |c1|
    c1.action do |global_options, options, args|
      help_now!('username is required') if args.empty?
      options[:username] = args.join(' ')

      puts "Creating client with username: #{options[:username]}\n"
      puts "Using public key: #{options['public_key']}\n\n"
      options[:name] ||= ask('Name: ')
      options[:email] ||= ask('Email: ')
      options[:password] ||= ask('Create a password: ') { |i| i.echo = '*' }

      help_now!('username is required') if options[:username].nil?
      help_now!('name is required') if options[:name].nil?
      help_now!('email is required') if options[:email].nil?
      help_now!('password is required') if options[:password].nil?
      help_now!('public_key is required') if options[:public_key].nil?

      exit_now!("#{options[:public_key]} does not exist" \
          ', please create one with ' \
          '`fenton keys generate -h`') unless File.exist?(options[:public_key])

      if @client.create_with_organization(global_options, options)
        if File.exist?("#{global_options[:directory]}/config")
          answer = ask('File exists ' \
            "#{global_options[:directory]}/config, overwrite? (y/N) ")
          exit_now!('Please choose a different path') unless answer =~ %r{^y$}i
        end

        @config.create(global_options, options)

        puts
        puts @client.message
      else
        exit_now!(@client.message)
      end
    end
  end
end

desc 'Organization Management'
arg_name 'organization'

command :organization do |c|
  c.default_desc 'Manage Organizations'

  c.flag [:key], desc: 'Key'

  @organization = Organization.new

  c.desc 'Create an organization (<name> --key)'
  arg_name 'create'

  c.command :create do |c1|
    c1.action do |global_options, options, args|
      help_now!('name is required') if args.empty?
      options[:name] = args.join(' ')

      help_now!('key is required') if options[:key].nil?

      puts "Creating organization with name: #{options[:name]}\n\n"

      if @organization.create(global_options, options)
        puts @organization.message
      else
        exit_now!(@organization.message)
      end
    end
  end
end

desc 'Project Management'
arg_name 'project'

command :project do |c|
  c.default_desc 'Manage Projects'

  c.flag [:key], desc: 'Key (unique short acronym for the project)'
  c.flag [:description], desc: 'Description'
  c.flag [:passphrase], desc: 'Passphrase'
  c.flag [:organization], desc: 'Organization Key (default: client username)'

  @project = Project.new

  c.desc 'Create a project (<name> --key --description ' \
    '--passphrase --organization)'
  arg_name 'create'

  c.command :create do |c1|
    c1.action do |global_options, options, args|
      help_now!('name is required') if args.empty?
      options[:name] = args.join(' ')

      options[:organization] ||= ConfigFile.default_organization(global_options)

      puts "Creating project #{options[:name]} " \
        "under organization #{options[:organization]}\n\n"

      options[:passphrase] ||= ask('Create a passphrase: ') { |i| i.echo = '*' }

      help_now!('key is required') if options[:key].nil?
      help_now!('description is required') if options[:description].nil?
      help_now!('passphrase is required') if options[:passphrase].nil?
      help_now!('organization key is required') if options[:organization].nil?

      if @project.create(global_options, options)
        puts @project.message
      else
        exit_now!(@project.message)
      end
    end
  end
end

desc 'SSH Key Management'
arg_name 'key'

command :key do |c|
  c.default_desc 'Manage your SSH Keys'

  @key = Key.new

  c.desc 'Generate a ssh key (--private_key --passphrase --type --bits)'
  arg_name 'generate'

  c.command :generate do |c1|
    c1.flag [:type], desc: 'RSA or DSA', default_value: 'rsa'
    c1.flag [:bits], desc: 'Size of key', type: Integer, default_value: 4096
    c1.flag [:private_key], desc: 'Private key file path to use',
                            default_value: private_key_default
    c1.flag [:passphrase], desc: 'Passphrase that protects the private key'

    c1.action do |_global_options, options, _args|
      puts "Generating key at #{options[:private_key]}"

      if File.exist?(options[:private_key])
        answer = ask('File exists, overwrite? (y/N) ')
        exit_now!('Please choose a different path') unless answer =~ %r{^y$}i
      end

      options[:passphrase] ||= ask('Enter passphrase: ') { |i| i.echo = '*' }

      if @key.create(options)
        puts @key.message
      else
        exit_now!(@key.message)
      end
    end
  end

  c.desc 'Sign your ssh public key (--project --client --public_key)'
  arg_name 'sign'

  c.command :sign do |c1|
    @certificate = Certificate.new

    c1.flag [:project], desc: 'Project key to sign with'

    c1.action do |global_options, options|
      options[:client] ||= ConfigFile.username(global_options)
      options[:public_key] ||= ConfigFile.public_key(global_options)

      help_now!('client username is required') if options[:client].nil?
      help_now!('project key is required') if options[:project].nil?
      help_now!('public key is required') if options[:public_key].nil?

      puts "Signing public key (#{options[:public_key]}) " \
      "for client: #{options[:client]}"

      if @certificate.create(global_options, options)
        puts @certificate.message
      else
        exit_now!(@certificate.message)
      end
    end
  end
end

desc 'Machine Management'
arg_name 'machine'

command :machine do |c|
  c.desc 'SSH to a machine <[user@]ip|host> (--private_key --command)'
  arg_name 'ssh'

  c.command :ssh do |c1|
    c1.flag [:private_key], desc: 'Private key file path to use',
                            default_value: private_key_default
    c1.flag [:command], desc: 'Command to run for non-interactive sessions'

    c1.action do |_global_options, options, args|
      help_now!('machine is required') if args.empty?
      options[:machine] = args.join(' ')

      command = "-n '#{options[:command]}'" if options[:command]
      ssh_command = "ssh -tt -q #{options[:machine]} " \
      "-i #{options[:private_key]} #{command}"

      if command
        puts `#{ssh_command}`
      else
        exec(ssh_command)
      end
    end
  end
end

on_error do |ex|
  puts ex
end

exit run(ARGV)
