class EmsCluster < ApplicationRecord
  include SupportsFeatureMixin
  include NewWithTypeStiMixin
  include EventMixin
  include TenantIdentityMixin
  include CustomActionsMixin

  acts_as_miq_taggable

  belongs_to  :ext_management_system, :foreign_key => "ems_id"
  has_many    :hosts, :dependent => :nullify
  has_many    :vms_and_templates, :dependent => :nullify
  has_many    :miq_templates, :inverse_of => :ems_cluster
  has_many    :vms, :inverse_of => :ems_cluster

  has_many    :metrics,                :as => :resource  # Destroy will be handled by purger
  has_many    :metric_rollups,         :as => :resource  # Destroy will be handled by purger
  has_many    :vim_performance_states, :as => :resource  # Destroy will be handled by purger

  has_many    :policy_events, -> { order("timestamp") }
  has_many    :miq_events,         :as => :target,   :dependent => :destroy
  has_many    :miq_alert_statuses, :as => :resource, :dependent => :destroy
  has_many    :host_hardwares, :class_name => 'Hardware', :through => :hosts, :source => :hardware
  has_many    :vm_hardwares, :class_name => 'Hardware', :through => :vms_and_templates, :source => :hardware
  has_many    :storages, -> { distinct }, :through => :hosts
  has_many    :lans, -> { distinct }, :through => :hosts

  has_many :switches, -> { distinct }, :through => :hosts

  virtual_column :v_ram_vr_ratio,      :type => :float,   :uses => [:aggregate_memory, :aggregate_vm_memory]
  virtual_column :v_cpu_vr_ratio,      :type => :float,   :uses => [:aggregate_cpu_total_cores, :aggregate_vm_cpus]
  virtual_column :v_parent_datacenter, :type => :string,  :uses => :all_relationships
  virtual_column :v_qualified_desc,    :type => :string,  :uses => :all_relationships
  virtual_total  :total_vms,               :vms
  virtual_total  :total_miq_templates,     :miq_templates
  virtual_total  :total_vms_and_templates, :vms_and_templates
  virtual_total  :total_hosts,             :hosts

  virtual_has_many :storages,       :uses => {:hosts => :storages}
  virtual_has_many :resource_pools, :uses => :all_relationships
  virtual_has_many :lans,           :uses => {:hosts => :lans}

  has_many :failover_hosts, -> { failover }, :class_name => "Host"

  delegate :queue_name_for_ems_operations, :to => :ext_management_system, :allow_nil => true

  include ProviderObjectMixin

  include FilterableMixin

  include DriftStateMixin
  virtual_delegate :last_scan_on, :to => "last_drift_state_timestamp_rec.timestamp", :allow_nil => true, :type => :datetime

  include RelationshipMixin
  self.default_relationship_type = "ems_metadata"

  include AggregationMixin
  include Metric::CiMixin
  include MiqPolicyMixin
  include AsyncDeleteMixin

  #
  # Provider Object methods - to be overwritten by Provider authors
  #
  def provider_object(_connection)
    raise NotImplementedError, _("provider_object must be implemented by a subclass")
  end

  def provider_object_release(_handle)
    raise NotImplementedError, _("provider_object_release must be implemented by a subclass")
  end

  def register_host(_host)
    raise NotImplementedError, _("register_host must be implemented by a subclass")
  end

  #
  # Virtual Column methods
  #

  def v_ram_vr_ratio
    total_memory = aggregate_memory.to_f
    total_memory == 0 ? 0 : (aggregate_vm_memory / total_memory * 10).round * 0.1
  end

  def v_cpu_vr_ratio
    total_cpus = aggregate_cpu_total_cores.to_f
    total_cpus == 0 ? 0 : (aggregate_vm_cpus / total_cpus * 10).round * 0.1
  end

  def v_parent_datacenter
    dc = parent_datacenter
    dc.nil? ? "" : dc.name
  end

  def v_qualified_desc
    dc = parent_datacenter
    dc.nil? ? name : "#{name} in #{dc.name}"
  end

  delegate :my_zone, :to => :ext_management_system

  def total_vcpus
    hosts.inject(0) { |c, h| c + (h.total_vcpus || 0) }
  end

  #
  # Relationship methods
  #

  # Direct Vm relationship methods
  def direct_vm_rels
    # Look for only the Vms at the second depth (default RP + 1)
    grandchild_rels(:of_type => 'VmOrTemplate')
  end

  def direct_vms
    Relationship.resources(direct_vm_rels).sort_by { |v| v.name.downcase }
  end

  alias_method :direct_miq_templates, :miq_templates

  def direct_vms_and_templates
    (direct_vms + direct_miq_templates).sort_by { |v| v.name.downcase }
  end

  def direct_vm_ids
    Relationship.resource_ids(direct_vm_rels)
  end

  alias_method :direct_miq_template_ids, :miq_template_ids

  def direct_vm_or_template_ids
    direct_vm_ids + direct_miq_template_ids
  end

  virtual_total :total_direct_vms, :direct_vm_rels
  virtual_total :total_direct_miq_templates, :miq_templates

  def total_direct_vms_and_templates
    total_direct_vms + total_direct_miq_templates
  end

  # Resource Pool relationship methods
  def default_resource_pool
    Relationship.resource(child_rels(:of_type => 'ResourcePool').first)
  end

  def resource_pools
    Relationship.resources(grandchild_rels(:of_type => 'ResourcePool'))
  end

  def resource_pools_with_default
    Relationship.resources(child_and_grandchild_rels(:of_type => 'ResourcePool'))
  end

  alias_method :add_resource_pool, :set_child
  alias_method :remove_resource_pool, :remove_child

  def remove_all_resource_pools
    remove_all_children(:of_type => 'ResourcePool')
  end

  # All RPs under this Cluster and all child RPs
  def all_resource_pools
    # descendants typically returns the default_rp first but sporadically it
    # will not due to a bug in the ancestry gem, this means we cannot simply
    # drop the first value and need to check is_default
    descendants(:of_type => 'ResourcePool').select { |r| !r.is_default }.sort_by { |r| r.name.downcase }
  end

  def all_resource_pools_with_default
    descendants(:of_type => 'ResourcePool').sort_by { |r| r.name.downcase }
  end

  # Parent relationship methods
  def parent_folder
    detect_ancestor(:of_type => "EmsFolder") { |a| !a.kind_of?(Datacenter) && !%w[host vm].include?(a.name) } # TODO: Fix this to use EmsFolder#hidden?
  end

  def parent_datacenter
    detect_ancestor(:of_type => 'EmsFolder') { |a| a.kind_of?(Datacenter) }
  end

  def event_where_clause(assoc = :ems_events)
    return ["ems_cluster_id = ?", id] if assoc.to_sym == :policy_events

    cond = ["ems_cluster_id = ?"]
    cond_params = [id]

    ids = host_ids
    unless ids.empty?
      cond << "host_id IN (?) OR dest_host_id IN (?)"
      cond_params += [ids, ids]
    end

    ids = vm_or_template_ids
    unless ids.empty?
      cond << "vm_or_template_id IN (?) OR dest_vm_or_template_id IN (?)"
      cond_params += [ids, ids]
    end

    cond_params.unshift(cond.join(" OR ")) unless cond.empty?
    cond_params
  end

  # TODO: ems_events already exists as a method but it uses
  # event_where_clause which tacks on additional possibly expensive queries
  # such as all events for all hosts or vms in the cluster.
  # Should we move to looking at ems events specifically for the ems cluster?
  # like this:
  #   has_many :ems_events, :foreign_key => :ems_cluster_id
  def ems_events
    ewc = event_where_clause
    return [] if ewc.blank?

    EmsEvent.where(ewc).order("timestamp").to_a
  end

  supports :smartstate_analysis

  def scan(_userid = "system")
    MiqQueue.submit_job(
      :service     => "smartstate",
      :affinity    => ext_management_system,
      :class_name  => self.class.to_s,
      :method_name => "save_drift_state",
      :instance_id => id
    )
  end

  def get_reserve(field)
    rp = default_resource_pool
    rp && rp.send(field)
  end

  def cpu_reserve
    get_reserve(:cpu_reserve)
  end

  def memory_reserve
    get_reserve(:memory_reserve)
  end

  def effective_resource(resource)
    resource = resource.to_s
    unless %w[cpu vcpu memory].include?(resource)
      raise ArgumentError, _("Unknown resource %{name}") % {:name => resource.inspect}
    end

    resource = "cpu" if resource == "vcpu"
    send(:"effective_#{resource}")
  end

  #
  # Metric methods
  #

  PERF_ROLLUP_CHILDREN = [:hosts]

  def perf_rollup_parents(interval_name = nil)
    [ext_management_system].compact unless interval_name == 'realtime'
  end

  def self.get_perf_collection_object_list
    cl_hash = in_my_region.includes(:tags, :taggings).select(:id, :name).each_with_object({}) do |c, h|
      h[c.id] = {:cl_rec => c, :ho_ids => c.host_ids}
    end

    hids = cl_hash.values.flat_map { |v| v[:ho_ids] }.compact.uniq
    hosts_by_id = Host.where(:id => hids).includes(:tags, :taggings).select(:id, :name, :vmm_vendor, :ems_cluster_id).index_by(&:id)

    cl_hash.each do |_k, v|
      hosts = hosts_by_id.values_at(*v[:ho_ids]).compact
      if hosts.empty?
        v[:ho_enabled] = v[:ho_disabled] = []
      else
        v[:ho_enabled], v[:ho_disabled] = hosts.partition(&:perf_capture_enabled?)
      end
    end

    cl_hash
  end

  def get_perf_collection_object_list
    hosts = hosts_enabled_for_perf_capture
    perf_capture_enabled? ? [self] + hosts : hosts
  end

  def perf_capture_enabled_host_ids=(ids)
    self.perf_capture_enabled = ids.any?
    hosts.each { |h| h.perf_capture_enabled = ids.include?(h.id) }
  end

  def hosts_enabled_for_perf_capture
    hosts(:include => [:taggings, :tags]).select(&:perf_capture_enabled?)
  end

  def self.display_name(number = 1)
    n_('Cluster', 'Clusters', number)
  end
end
