#!/usr/bin/env oo-ruby
require 'getoptlong'
require 'pp'

CTL_DISTRICT_COMMANDS = %w[list-available create add-node set-region unset-region deactivate-node activate-node remove-node add-capacity remove-capacity destroy publish-uids ]

def usage
  puts <<USAGE
== Synopsis

oo-admin-ctl-district: Control districts

== Usage

oo-admin-ctl-district OPTIONS

Options:
-c|--command <command>
    (#{CTL_DISTRICT_COMMANDS * '|'})
-u|--uuid <district uuid>
    District uuid  (alphanumeric)
-n|--name <district name>
    District name (Used on create or in place of uuid on other commands)
    Allowed chars: alphanumeric, underscore, hyphen, dot
-p|--node_profile <gear_size>
    Only required for create
-i|--server_identity
    Node server identity (required) (may be a comma-separated list)
-a|--available
    On add-node, add all available nodes with the right profile
-r|--region <region_name>
    Region name of the server identitiy.
    Only valid for add-node (optional) and set-region
-z|--zone <zone_name>
    Zone within the region. Only valid when region is specified
-s|--size
    Amount of capacity to add or remove (positive number) (required)
-b|--bypass
    Ignore warnings
-h|--help
    Show usage info
USAGE
  exit 255
end

def append_district(district, io)
  district.available_uids = "<#{district.available_uids.length} uids hidden>"
  io << "\n\n#{district.attributes.pretty_inspect}"
end

def get_district(uuid, name)
  if uuid
    district = District.where(uuid: uuid).first
  else
    district = District.find_by_name(name)
  end
end

def available_nodes
  return @nodes_for_profile if @nodes_for_profile
  @nodes_for_profile = Hash.new {|h,k| h[k] = [] }
  OpenShift::ApplicationContainerProxy.get_details_for_all(%w[node_profile district_uuid]).each do |host,details|
    next unless details[:district_uuid] == 'NONE'
    @nodes_for_profile[details[:node_profile]] << host
  end
  @nodes_for_profile
end

opts = GetoptLong.new(
    ["--command",          "-c", GetoptLong::REQUIRED_ARGUMENT],
    ["--uuid",             "-u", GetoptLong::REQUIRED_ARGUMENT],
    ["--node_profile",     "-p", GetoptLong::REQUIRED_ARGUMENT],
    ["--name",             "-n", GetoptLong::REQUIRED_ARGUMENT],
    ["--server_identity",  "-i", GetoptLong::REQUIRED_ARGUMENT],
    ["--available",        "-a", GetoptLong::NO_ARGUMENT],
    ["--region",           "-r", GetoptLong::REQUIRED_ARGUMENT],
    ["--zone",             "-z", GetoptLong::REQUIRED_ARGUMENT],
    ["--size",             "-s", GetoptLong::REQUIRED_ARGUMENT],
    ["--bypass",           "-b", GetoptLong::NO_ARGUMENT],
    ["--help",             "-h", GetoptLong::NO_ARGUMENT]
)

args = {}
begin
  opts.each{ |k,v| args[k]=v }
rescue GetoptLong::Error => e
  usage
end

if args["--help"]
  usage
end

uuid     = args["--uuid"]
command  = args['--command']
if server_identity = args['--server_identity']
  server_identity = server_identity.split /[,;:\s]\s*/
end
name     = args['--name']
available = args['--available']
region   = args['--region']
zone     = args['--zone']
size     = args['--size'] ? args['--size'].to_i : nil
bypass   = args['--bypass']
node_profile = args['--node_profile']

if !command && (args.length > 0)
  puts "Error: --command is required with any arguments"
  exit 255
end

if command && !CTL_DISTRICT_COMMANDS.include?(command)
  puts "Command must be one of: #{CTL_DISTRICT_COMMANDS * '|'}"
  exit 255
end

require "#{ENV['OPENSHIFT_BROKER_DIR'] || '/var/www/openshift/broker'}/config/environment"
# Disable analytics for admin scripts
Rails.configuration.analytics[:enabled] = false

if node_profile && !Gear::valid_gear_size?(node_profile)
  puts "Node profile must be one of: #{Gear::gear_sizes_display_string}"
  exit 1
end

district = nil
if uuid || name
  district = get_district(uuid, name)
  if !district
    if command == 'add-node' && !node_profile
      puts "District '#{uuid ? uuid : name}' not found, and no profile given to auto-create."
      exit 1
    elsif ! %w[create add-node].include?(command)
      puts "District '#{uuid ? uuid : name}' not found."
      exit 1
    end
  elsif command == 'create'
    puts "District '#{name}' already exists"
    exit 1
  end
  if %w[remove-node deactivate-node activate-node set-region unset-region].include?(command) && !server_identity
    puts "--server_identity is required with command: #{command}"
    exit 1
  end
  if command == 'add-node' && !(server_identity || available)
    puts "--server_identity or --available is required with command: #{command}"
    exit 1
  end
  unless region || (command != 'set-region')
    puts "--region is required with command: #{command}"
    exit 1
  end
  unless zone || (command != 'set-region')
    puts "--zone is required with command: #{command}"
    exit 1
  end
  unless size || (command != 'add-capacity' && command != 'remove-capacity')
    puts "--size is required with command: #{command}"
    exit 1
  end
elsif command && ! %w[list-available publish-uids].include?(command)
  if command != 'create'
    puts "--uuid or --name is required with command: #{command}"
  else
    puts "--name is required with create"
  end
  exit 1
end

def collate_errors(servers, reply, &block)
  servers.each do |server|
    begin
      yield(server)
      reply.resultIO << "Success for node '#{server}'!\n"
    rescue OpenShift::OOException => e
      reply.errorIO << "Error for node '#{server}': #{e.message}\n"
      reply.exitcode = e.code && e.respond_to?('code') ? e.code : 1
    end
  end
end

reply = ResultIO.new
begin
  case command
  when "list-available"
    available_nodes.each do |profile, nodes|
      next if node_profile && profile != node_profile
      reply.resultIO << "Nodes in profile: #{profile}\n"
      nodes.each {|node| reply.resultIO << "\t#{node}\n"}
    end
    if node_profile && available_nodes[node_profile].empty?
      reply.resultIO << "No nodes in profile: #{node_profile}\n"
    end
  when "add-node"
    unless district
      district = District::create_district(name, node_profile)
      uuid = district.uuid
      reply.resultIO.string.empty? && reply.resultIO << "Successfully created district: #{district.uuid}\n"
    end
    if available
      node_profile ||= district.gear_size
      server_identity = available_nodes[node_profile]
      if server_identity.empty?
        reply.errorIO << "No available nodes for profile '#{node_profile}'\n"
        reply.exitcode = 1
      end
    end
    collate_errors(server_identity, reply) {|server| district.add_node(server, region, zone) }
  when "set-region"
    collate_errors(server_identity, reply) {|server| district.set_region(server, region, zone) }
  when "unset-region"
    collate_errors(server_identity, reply) {|server| district.unset_region(server) }
  when "deactivate-node"
    collate_errors(server_identity, reply) {|server| district.deactivate_node(server) }
  when "remove-node"
    collate_errors(server_identity, reply) {|server| district.remove_node(server) }
  when "activate-node"
    collate_errors(server_identity, reply) {|server| district.activate_node(server) }
  when "add-capacity"
    district.add_capacity(size)
    reply.resultIO << "Success!"
  when "remove-capacity"
    district.remove_capacity(size)
    reply.resultIO << "Success!"
  when "publish-uids"
    districts = district ? [district] : District.find_all

    reply.resultIO << "No districts created yet.  Use 'oo-admin-ctl-district -c create' to create one." if districts.empty?
    districts.each do |district|
      if district.servers.empty?
        reply.resultIO << "District: #{district.name} does not have any server identities, skipping.\n"
      else
        reply.resultIO << "Publishing district UIDs for district: #{district.name} (#{district.uuid})\n"
        OpenShift::ApplicationContainerProxy.set_district_uid_limits("#{district.uuid}", district.first_uid, district.max_uid)
        reply.resultIO << "District: #{district.name} done\n"
      end
    end
  when "create"
    default_gear_size = Rails.application.config.openshift[:default_gear_size]
    puts "node_profile not specified.  Using default: #{default_gear_size}" unless node_profile
    district = District::create_district(name, node_profile)
    uuid = district.uuid
    reply.resultIO << "Successfully created district: #{district.uuid}" if reply.resultIO.string.empty?
  when "destroy"
    unless bypass
      puts <<-WARNING
!!!! WARNING !!!! WARNING !!!! WARNING !!!!
You are about to delete the #{uuid ? uuid : name} district.

This is NOT reversible, all remote data for this district will be removed.
WARNING

      print "Do you want to delete this district (y/n): "
      begin
        unless gets.to_s.strip =~ /^(yes|y)$/i
          puts "\n"
          exit 217
        end
      rescue Interrupt
        puts "\n"
        exit 217
      end
    end
    district.delete
    reply.resultIO << "Successfully deleted district: #{uuid ? uuid : name}" if reply.resultIO.string.empty?
  else
    if district
      append_district(district, reply.resultIO)
    else
      districts = District.find_all
      unless districts.empty?
        districts.each do |district|
          append_district(district, reply.resultIO)
        end
      else
        puts "No districts created yet.  Use 'oo-admin-ctl-district -c create' to create one."
      end
    end
  end
  if (uuid || name) && command && command != 'destroy' && command != 'publish-uids'
    district = get_district(uuid, name)
    append_district(district, reply.resultIO)
  end
rescue OpenShift::OOException => e
  reply.errorIO << e.message
  if e.respond_to?('code') and e.code
    reply.exitcode = e.code
  else
    reply.exitcode = 1
  end
end

puts "DEBUG OUTPUT:\n#{reply.debugIO.string}\n" unless reply.debugIO.string.empty? # unused...
puts "#{reply.resultIO.string}\n" unless reply.resultIO.string.empty?
puts "ERROR OUTPUT:\n#{reply.errorIO.string}\n" unless reply.errorIO.string.empty?
exit reply.exitcode
