#!/usr/bin/env oo-ruby

require 'rubygems'
require 'getoptlong'

TIME_FORMAT_REGEX = /^\d{4}\-\d{1,2}\-\d{1,2}$/

def show_usage
    puts <<USAGE
== Synopsis

oo-admin-usage: List the usage data for a user. 
Note: The usage cost displayed is based on the usage duration and the usage rate. 
It does not take into account any unbilled usage by the billing provider.

== Usage

oo-admin-usage OPTIONS

Options:
-l|--login
    The user login whose usage data is to be displayed (required)
-a|--app
    The application name to filter the usage data 
-g|--gear
    The gear id to filter the usage data 
-s|--start
    The start date (yyyy-mm-dd) to filter the usage data (defaults to start of current month)
-e|--end
    The end date (yyyy-mm-dd) to filter the usage data (defaults to the current time)
-h|--help
    Show Usage info
USAGE
  exit 255
end

def calculate_usage_cost(usage_rate, duration)
  cost = nil
  
  unless usage_rate.nil?
    if usage_rate[:duration] == :hour
      duration_in_hr =  duration / (60 * 60)
      cost = duration_in_hr * usage_rate[:usd]
    elsif usage_rate[:duration] == :day
      duration_in_day =  duration / (60 * 60 * 24)
      cost = duration_in_day * usage_rate[:usd]
    elsif usage_rate[:duration] == :month
      duration_in_month =  duration / (60 * 60 * 24 * 30)
      cost = duration_in_month * usage_rate[:usd]
    end
  end
  cost
end

def pretty_duration(duration)
  secs  = duration.to_int
  mins  = secs / 60
  hours = mins / 60
  days  = hours / 24

  if days > 0
    "#{days} days and #{hours % 24} hours"
  elsif hours > 0
    "#{hours} hours and #{mins % 60} minutes"
  elsif mins > 0
    "#{mins} minutes and #{secs % 60} seconds"
  elsif secs >= 0
    "#{secs} seconds"
  end
end

def formatted_number(n, options={})
  options = {
    :precision => 2,
    :separator => '.',
    :delimiter => ',',
    :format => "$%s"
  }.merge(options)

  a,b = sprintf("%0.#{options[:precision]}f", n).split('.')
  a.gsub!(/(\d)(?=(\d{3})+(?!\d))/, "\\1#{options[:delimiter]}")
  sprintf(options[:format], "#{a}#{options[:separator]}#{b}")
end


opts = GetoptLong.new(
    ["--login", "-l", GetoptLong::REQUIRED_ARGUMENT],
    ["--gear",  "-g", GetoptLong::REQUIRED_ARGUMENT],
    ["--app",   "-a", GetoptLong::REQUIRED_ARGUMENT],
    ["--start", "-s", GetoptLong::REQUIRED_ARGUMENT],
    ["--end",   "-e", GetoptLong::REQUIRED_ARGUMENT],
    ["--help",  "-h", GetoptLong::NO_ARGUMENT]
)

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

if args["--help"]
  show_usage
end
login = args["--login"]
app = args["--app"]
gear = args["--gear"]
start_timeframe = args["--start"]
end_timeframe = args["--end"]

unless login
  puts "You must specify the user in order to display the usage details"
  show_usage
end

# load the rails environment
require "/var/www/openshift/broker/config/environment"

# record current time so that it remains constant throughout the execution of the script
current_time = Time.now

unless start_timeframe.nil?
  begin
    raise unless start_timeframe =~ TIME_FORMAT_REGEX
    start_timeframe = Time.parse(start_timeframe).to_i
  rescue Exception => ex
    puts "Please specify the start time as yyyy-mm-dd"
    show_usage
  end
end

unless end_timeframe.nil?
  begin
    raise unless end_timeframe =~ TIME_FORMAT_REGEX
    end_timeframe = Time.parse(end_timeframe).to_i
    if end_timeframe > current_time.to_i
      puts "End timeframe specified is greater than the current time. Defaulting to current time..."
      end_timeframe = current_time.to_i
    end
  rescue Exception => ex
    puts "Please specify the end time as yyyy-mm-dd"
    show_usage
  end
end

# default start timeframe to start of current month if, 
# start timeframe is not specified 
# and end timeframe is specified
# and end timeframe is earlier than the start of the current month   
if start_timeframe.nil? && end_timeframe && end_timeframe > current_time.at_beginning_of_month.to_i
  start_timeframe = current_time.at_beginning_of_month.to_i
end

if start_timeframe && end_timeframe && start_timeframe > end_timeframe
  puts "The end time cannot be greater than the start time"
  show_usage
end

# Disable analytics for admin scripts
Rails.configuration.analytics[:enabled] = false
puts

begin
  user = CloudUser::find_by(login: login)
rescue Mongoid::Errors::DocumentNotFound
  puts "Error: User '#{login}' not found"
  exit 5
end

puts "Usage for #{login} (Plan: #{user.plan_id})" if user.plan_id
puts "Usage for #{login}" unless user.plan_id
puts "------------------------------------------"

filter = {"app_name" => app, "gear_id" => gear, "begin_time" => start_timeframe, "end_time" => end_timeframe}
usage_data = Usage.find_by_filter(user._id, filter)
if usage_data.count > 0
  usage_data.each_with_index do |usage, index|
    # calculate the usage start time
    if start_timeframe.nil?
      begin_time = usage.begin_time
    else
      begin_time = [usage.begin_time, Time.at(start_timeframe)].max
    end
    
    # calculate the usage end time
    if end_timeframe.nil?
      if usage.end_time.nil?
        end_time = current_time
        end_time_str = "PRESENT"
      else
        end_time = usage.end_time
      end
    else
      if usage.end_time.nil?
        end_time = Time.at(end_timeframe)
      else
        end_time = [usage.end_time, Time.at(end_timeframe)].min
      end
    end

    duration = end_time - begin_time
    duration_str = pretty_duration(duration)
    begin_time_str = begin_time.localtime.strftime('%Y-%m-%d %H:%M:%S')
    end_time_str = end_time.localtime.strftime('%Y-%m-%d %H:%M:%S') unless end_time_str == "PRESENT" 

    # calculate the usage cost
    usage_rate = usage.get_usage_rate(user.plan_id)
    usage_cost = calculate_usage_cost(usage_rate, duration)

    if usage.usage_type == UsageRecord::USAGE_TYPES[:gear_usage]
      usage_qualifier = usage.gear_size
    elsif usage.usage_type == UsageRecord::USAGE_TYPES[:addtl_fs_gb]
      usage_qualifier = usage.addtl_fs_gb
    elsif usage.usage_type == UsageRecord::USAGE_TYPES[:premium_cart]
      usage_qualifier = usage.cart_name
    end

    # print out the user's usage data
    puts "##{index + 1}"
    puts " Usage Type: #{usage.usage_type} (#{usage_qualifier})"
    puts "    Gear ID: #{usage.gear_id} (#{usage.app_name})"
    puts "   Duration: #{duration_str} (#{begin_time_str} - #{end_time_str})"
    puts "Cost (Est.): #{formatted_number(usage_cost)} ($#{usage_rate[:usd]}/#{usage_rate[:duration].to_s})" unless usage_rate.nil?
    puts
  end

  puts "Note: The cost displayed is based on the usage duration and the usage rate. " \
  "It does not take into account any unbilled usage by the billing provider." unless user.plan_id
  puts
else
  puts "No usage data found"
  puts
end

