require 'ordu'
require 'cloudflarer'
require 'yaml'
require 'mustache'

module Cloudflarer
  class CLI < Ordu
    option('-h', '--help', 'show this message') do
      puts to_s
      exit
    end
    option('-k', '--api-key API_KEY', 'specify the API key') do |key|
      CLOUDFLARE_API_KEY = key
    end
    option('-e', '--email EMAIL', 'specify the Cloudflare Email') do |email|
      CLOUDFLARE_EMAIL = email
    end
    option('-v', '--[no-]verbose', 'print request/response') do |v|
      $verbose = v
    end
    option('-q', '--[no-]quiet', 'print less information') do |q|
      $quiet = q
    end
    option('-D', '--[no-]debug', 'print lots of internal info') do |d|
      $debug = d
    end
    option('-V', '--version', 'show the version number') do |v|
      puts "Cloudflarer v#{Cloudflarer::VERSION}"
      puts "Cloudflare API version #{Cloudflarer::API_VERSION}"
      exit
    end
    option('-f', '--format FMT', 'specify output format') { |f| template(f) }
    command 'user', 'manage your user' do
      command 'update', 'change user properties' do
        option('--first_name NAME', 'First name') { |v| set(first_name: v) }
        option('--last_name NAME', 'Last name') { |v| set(last_name: v) }
        option('--telephone PHONE', 'Telephone') { |v| set(telephone: v) }
        option('--country ISO', 'Country') { |v| set(country: v) }
        option('--zipcode ZIP', 'Zipcode') { |v| set(zipcode: v) }
        action { update('user') }
      end

      action { show('user') }
    end

    command 'zones', 'manage your zones' do
      command 'show', 'show a zone' do
        action do |*ids|
          die("You need to specify the zone ID") if ids.empty?
          ids.each { |z| show("zones/#{z}") }
        end
      end
      command 'create', 'create a zone' do
        option('--[no-]jump_start', 'auto-fetch DNS records') do |j|
          set(:jump_start, j)
        end
        option('--name DOMAIN', 'domain name') { |v| set(name: v) }
        action { |name| post('zones' ) }
      end
      action { list('zones') }
    end

    command 'records', 'manage DNS records' do
      option('-z', '--zone ID', 'specify the zone') { |z| set(zone: z) }
      command 'show', 'show a DNS record' do
        action do |*ids|
          die("You need to specify the record ID") if ids.empty?
          template('{{id}} {{type}} {{name}} {{content}}')
          zone { |z| ids.each { |r| show("zones/#{z}/dns_records/#{r}") } }
        end
      end
      command 'create', 'create a new DNS record' do
        option('-z', '--zone ID', 'specify the zone') { |z| set(zone: z) }
        option('--type TYPE', 'DNS record type') { |v| set(type: v) }
        option('--name NAME', 'DNS record name') { |v| set(name: v) }
        option('--content ADDR', 'DNS record content') { |v| set(content: v) }
        action do
          zone { |z| create("zones/#{z}/dns_records") }
        end
      end
      command 'delete', 'delete a DNS record' do
        option('-z', '--zone ID', 'specify the zone') { |z| set(zone: z) }
        action do |*ids|
          die('You need to specify te record ID') if ids.empty?
          zone { |z| ids.each { |r| destroy("zones/#{z}/dns_records/#{r}") } }
        end
      end

      action do
        template('{{id}} {{type}} {{name}} {{content}}')
        zone { |z| show("zones/#{z}/dns_records") }
      end
    end

    # Yields the zone popped from the params, or dies
    def zone(&block)
      @zone ||= params.delete(:zone)
      die('You need to specify the zone (-z)') unless @zone
      yield @zone
    end

    # A place to gather params for queries
    def params
      @params ||= {}
    end

    # Sets a param for a query (used by options)
    def set(params = {})
      self.params.merge!(params)
    end

    # Creates a record (using params)
    def create(path)
      output { post(path, params) }
    end

    # Updates a record (using params)
    def update(path)
      output { patch(path, params) }
    end

    # Gets and shows a single resource
    def show(path)
      output { get(path) }
    end

    # Deletes and shows a resource
    def destroy(path)
      output { delete(path) }
    end
      
    # Gets and lists multiple resources
    def list(path)
      output { get(path) }
    end

    # Gets a resource
    def get(path)
      time("GET #{path}") { Cloudflarer.new.get(path) }
    end

    # Updates a resource (using params)
    def patch(path, params)
      time("PATCH #{path}") { Cloudflarer.new.patch(path, params) }
    end

    # Creates a resource (using params)
    def post(path, params)
      time("POST #{path}") { Cloudflarer.new.post(path, params) }
    end

    # Destroys a resource
    def delete(path)
      time("DELETE #{path}") { Cloudflarer.new.delete(path) }
    end

    # Times the block, which should return something with a status
    def time(msg, &block)
      return(yield) unless $verbose
      print "#{msg}..."
      t = Time.now.to_f
      response = yield
      print "(%0.2f ms) " % (Time.now.to_f - t)
      if info = response['result_info']
        print "[%s/%s/%s/%s] " %
          info.values_at(*%w(page per_page count total_count)).map(&:to_s)
      end
      puts "OK" if response.fetch('success')
      puts "FAIL" unless response.fetch('success')
      response
    end

    # Gets the formatter
    def format(&block)
      object = yield
      return object if object.is_a?(String)
      return object.to_yaml if template == 'yaml'
      return object.to_json if template == 'json'
      return render(template, object) if template.is_a?(String)
      raise "Invalid template: #{template}"
    end

    # Gets the template with which to present the object
    def template(template = nil)
      $template ||= template
      $template || '{{id}} {{name}}'
    end

    # Renders the given object through the Mustache template
    def render(template, object)
      return object.map { |o| render(template, o) } if object.is_a?(Array)
      Mustache.render(template, object)
    end

    # Filters results, if required. Currently doesn't do anything.
    def filter(&block)
      result = yield
      return result.map { |o| filter { o } } if result.is_a?(Array)
      result
    end

    # Outputs in the format requested
    def output(&block)
      response = yield
      if response.fetch('success')
        puts format { filter { response.fetch('result') } }
      else
        puts format { response.fetch('error') }
      end
    end

    # Print the result of the block, if $debug is on
    def debug(msg, &block)
      result = yield
      puts "-- #{msg} #{'-' * (74 - msg.length)}" if $debug
      result
    end

    # Print the message to STDERR and exit (non-zero)
    def die(msg, code = 1)
      STDERR.puts msg
      exit code
    end
  end
end

if __FILE__ == $0
  Cloudflarer::CLI.parse!(ARGV)
end
