require 'uri'

module Foreman::Model
  class Ovirt < ComputeResource

    validates :url, :format => { :with => URI.regexp }
    validates :user, :password, :presence => true

    alias_attribute :datacenter, :uuid

    def self.model_name
      ComputeResource.model_name
    end

    def capabilities
      [:build]
    end

    def supports_update?
      true
    end

    def provided_attributes
      super.merge({:mac => :mac})
    end

    #FIXME
    def max_cpu_count
      8
    end

    def max_memory
      16*1024*1024*1024
    end

    def quotas
      client.quotas
    end

    def ovirt_quota=(ovirt_quota_id)
      self.attrs[:ovirt_quota_id] = ovirt_quota_id
    end

    def ovirt_quota
      self.attrs[:ovirt_quota_id]
    end

    def templates(opts={})
      client.templates
    end

    def template(id)
      client.templates.get(id) || raise(ActiveRecord::RecordNotFound)
    end

    def clusters
      client.clusters
    end

    # Check if HTTPS is mandatory, since rest_client will fail with a POST
    def test_https_required
      RestClient.post url, {} if URI(url).scheme == 'http'
      true
    rescue => e
      case e.message
      when /406/
        true
      else
        raise e
      end
    end
    private :test_https_required

    def test_connection options = {}
      super
      errors[:url].empty? && datacenters && test_https_required
    rescue => e
      case e.message
        when /404/
          errors[:url] << e.message
        when /302/
          errors[:url] << 'HTTPS URL is required for API access'
        when /401/
          errors[:user] << e.message
        else
          errors[:base] << e.message
      end
    end

    def datacenters(options={})
      client.datacenters(options).map { |dc| [dc[:name], dc[:id]] }
    end

    def networks(opts ={})
      if opts[:cluster_id]
        client.clusters.get(opts[:cluster_id]).networks
      else
        []
      end
    end

    def storage_domains(opts ={})
      client.storage_domains({:role => 'data'}.merge(opts))
    end

    def start_vm(uuid)
      find_vm_by_uuid(uuid).start(:blocking => true)
    end

    def create_vm(args = {})
      #ovirt doesn't accept '.' in vm name.
      args[:name] = args[:name].parameterize
      vm = super({ :first_boot_dev => 'network', :quota => ovirt_quota }.merge(args))
      begin
        create_interfaces(vm, args[:interfaces_attributes])
        create_volumes(vm, args[:volumes_attributes])
      rescue => e
        destroy_vm vm.id
        raise e
      end
      vm
    end

    def new_vm(attr={})
      vm = super
      interfaces = nested_attributes_for :interfaces, attr[:interfaces_attributes]
      interfaces.map{ |i| vm.interfaces << new_interface(i)}
      volumes = nested_attributes_for :volumes, attr[:volumes_attributes]
      volumes.map{ |v| vm.volumes << new_volume(v)}
      vm
    end

    def new_interface(attr={})
      Fog::Compute::Ovirt::Interface.new(attr)
    end

    def new_volume(attr={})
      Fog::Compute::Ovirt::Volume.new(attr)
    end

    def save_vm(uuid, attr)
      vm = find_vm_by_uuid(uuid)
      vm.attributes.merge!(attr.symbolize_keys)
      update_interfaces(vm, attr[:interfaces_attributes])
      update_volumes(vm, attr[:volumes_attributes])
      vm.interfaces
      vm.volumes
      vm.save
    end

    def destroy_vm(uuid)
      begin
        find_vm_by_uuid(uuid).destroy
      rescue OVIRT::OvirtException => e
        #404 error are ignored on delete.
        raise e unless e.message =~ /404/
      end
      true
    end

    def console(uuid)
      vm = find_vm_by_uuid(uuid)
      raise "VM is not running!" if vm.status == "down"
      if vm.display[:type] =~ /spice/i
        xpi_opts = {:name => vm.name, :address => vm.display[:address], :secure_port => vm.display[:secure_port], :ca_cert => cacert, :subject => vm.display[:subject] }
        opts = if vm.display[:secure_port]
                 { :host_port => vm.display[:secure_port], :ssl_target => true }
               else
                 { :host_port => vm.display[:port] }
               end
        WsProxy.start(opts.merge(:host => vm.display[:address], :password => vm.ticket)).merge(xpi_opts).merge(:type => 'spice')
      else
        WsProxy.start(:host => vm.display[:address], :host_port => vm.display[:port], :password => vm.ticket).merge(:name => vm.name, :type => 'vnc')
      end
    end

    def update_required?(old_attrs, new_attrs)
      return true if super(old_attrs, new_attrs)

      new_attrs[:interfaces_attributes].each do |key, interface|
        return true if (interface[:id].blank? || interface[:_delete] == '1') && key != 'new_interfaces' #ignore the template
      end if new_attrs[:interfaces_attributes]

      new_attrs[:volumes_attributes].each do |key, volume|
        return true if (volume[:id].blank? || volume[:_delete] == '1') && key != 'new_volumes' #ignore the template
      end if new_attrs[:volumes_attributes]

      false
    end

    def associated_host(vm)
      Host.my_hosts.where(:mac => vm.mac).first
    end

    def provider_friendly_name
      "oVirt"
    end

    protected

    def bootstrap(args)
      client.servers.bootstrap vm_instance_defaults.merge(args.to_hash)
    rescue Fog::Errors::Error => e
      errors.add(:base, e.to_s)
      false
    end


    def client
      @client ||= ::Fog::Compute.new(
          :provider         => "ovirt",
          :ovirt_username   => user,
          :ovirt_password   => password,
          :ovirt_url        => url,
          :ovirt_datacenter => uuid
      )
    end

    def api_version
      @api_version ||= client.send(:client).api_version
    end

    def cacert
      ca_url = URI.parse(url)
      ca_url.path = "/ca.crt"
      http = Net::HTTP.new(ca_url.host, ca_url.port)
      http.use_ssl = (ca_url.scheme == 'https')
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE
      request = Net::HTTP::Get.new(ca_url.path)
      http.request(request).body
    end

    private
    def create_interfaces(vm, attrs)
      #first remove all existing interfaces
      vm.interfaces.each do |interface|
        #The blocking true is a work-around for ovirt bug, it should be removed.
        vm.destroy_interface(:id => interface.id, :blocking => true)
      end if vm.interfaces
      #add interfaces
      interfaces = nested_attributes_for :interfaces, attrs
      interfaces.map{ |i| vm.add_interface(i)}
      vm.interfaces.reload
    end

    def create_volumes(vm, attrs)
      #add volumes
      volumes = nested_attributes_for :volumes, attrs
      #The blocking true is a work-around for ovirt bug fixed in ovirt version 3.1.
      volumes.map{ |vol| vm.add_volume({:bootable => 'false', :quota => ovirt_quota, :blocking => api_version.to_f < 3.1}.merge(vol)) if vol[:id].blank?}
      vm.volumes.reload
    end

    def update_interfaces(vm, attrs)
      interfaces = nested_attributes_for :interfaces, attrs
      interfaces.each do |interface|
          vm.destroy_interface(:id => interface[:id]) if interface[:_delete] == '1' && interface[:id]
          vm.add_interface(interface) if interface[:id].blank?
      end
    end

    def update_volumes(vm, attrs)
      volumes = nested_attributes_for :volumes, attrs
      volumes.each do |volume|
        vm.destroy_volume(:id => volume[:id], :blocking => api_version.to_f < 3.1) if volume[:_delete] == '1' && volume[:id].present?
        vm.add_volume({:bootable => 'false', :quota => ovirt_quota, :blocking => api_version.to_f < 3.1}.merge(volume)) if volume[:id].blank?
      end
    end

  end
end
