require 'net/ssh'
require 'net/scp'
require 'fog'
require 'rspec-system/node_set/base'

module RSpecSystem
  class NodeSet::Openstack < NodeSet::Base
    PROVIDER_TYPE = 'openstack'
    attr_accessor :vmconf

    CONFIG_KEYS = [
      :node_timeout,
      :username,
      :flavor_name,
      :image_name,
      :endpoint,
      :keypair_name,
      :network_name,
      :ssh_keys,
      :api_key
    ]

    def initialize(name, config, custom_prefabs_path, options)
      super
      @vmconf = read_config
      @now = Time.now.strftime '%Y%m%d-%H:%M:%S.%L'
      RSpec.configuration.rs_storage[:nodes] ||= {}
    end

    def launch
      nodes.each do |k,v|
        storage = RSpec.configuration.rs_storage[:nodes][k] ||= {}
        options = {
          :flavor_ref => flavor.id,
          :image_ref => image.id,
          :name => "#{k}-#{@now}",
          :key_name => vmconf[:keypair_name]
        }
        options[:nics] = [{'net_id' => nic.id}] if vmconf[:network_name]
        log.info "Launching openstack instance #{k}"
        result = compute.servers.create options
        storage[:server] = result
      end
    end

    def connect
      nodes.each do |k,v|
        server = RSpec.configuration.rs_storage[:nodes][k][:server]
        before = Time.new.to_i
        while true
          begin
            server.wait_for(5) { ready? }
            break
          rescue ::Fog::Errors::TimeoutError
            raise if Time.new.to_i - before > vmconf[:node_timeout]
            log.info "Timeout connecting to instance, trying again..."
          end
        end

        chan = ssh_connect(:host => k, :user => 'root', :net_ssh_options => {
          :keys => vmconf[:ssh_keys].split(':'),
          :host_name => server.addresses[vmconf[:network_name]].first['addr'],
          :paranoid => false
        })
        RSpec.configuration.rs_storage[:nodes][k][:ssh] = chan
      end
    end

    def teardown
      nodes.keys.each do |k|
        server = RSpec.configuration.rs_storage[:nodes][k][:server]
        log.info "Destroying server #{server.name}"
        server.destroy
      end
    end

    def compute
      @compute || @compute = Fog::Compute.new({
        :provider => :openstack,
        :openstack_username => vmconf[:username],
        :openstack_api_key => vmconf[:api_key],
        :openstack_auth_url => vmconf[:endpoint],
      })
    end

    def network
      @network || @network = Fog::Network.new({
        :provider => :openstack,
        :openstack_username => vmconf[:username],
        :openstack_api_key => vmconf[:api_key],
        :openstack_auth_url => vmconf[:endpoint],
      })
    end
    private

    def flavor
      log.info "Looking up flavor #{vmconf[:flavor_name]}"
      compute.flavors.find { |x| x.name == vmconf[:flavor_name] } || raise("Couldn't find flavor: #{vmconf[:flavor_name]}")
    end

    def image
      log.info "Looking up image #{vmconf[:image_name]}"
      compute.images.find { |x| x.name == vmconf[:image_name] } || raise("Couldn't find image: #{vmconf[:image_name]}")
    end

    def nic
      log.info "Looking up network #{vmconf[:network_name]}"
      network.networks.find { |x| x.name == vmconf[:network_name] } || raise("Couldn't find network: #{vmconf[:network_name]}")
    end

    def read_config
      conf = ENV.inject({}) do |memo,(k,v)|
        if k =~ /^RS_OPENSTACK_(.+)/
          var = $1.downcase.to_sym
          memo[var] = v if ([var] & CONFIG_KEYS).any?
        end
        memo
      end

      conf[:node_timeout] = conf[:node_timeout].to_i unless conf[:node_timeout].nil?
      conf
    end
  end
end