require 'miq-encode'

class OperatingSystem < ApplicationRecord
  belongs_to :host
  belongs_to :vm_or_template
  belongs_to :vm,           :foreign_key => :vm_or_template_id
  belongs_to :miq_template, :foreign_key => :vm_or_template_id
  belongs_to :computer_system
  has_many   :processes, :class_name => 'OsProcess', :dependent => :destroy
  has_many   :event_logs, :dependent => :destroy
  has_many   :firewall_rules, :as => :resource, :dependent => :destroy

  OS_MAP = [
    ["windows_generic", %w[winnetenterprise w2k3 win2k3 server2003 winnetstandard servernt]],
    ["windows_generic", %w[winxppro winxp]],
    ["windows_generic", %w[vista longhorn]],
    ["windows_generic", %w[win2k win2000]],
    ["windows_generic", %w[microsoft windows winnt]],
    ["linux_ubuntu",    %w[ubuntu]],
    ["linux_chrome",    %w[chromeos]],
    ["linux_chromium",  %w[chromiumos]],
    ["linux_suse",      %w[suse sles]],
    ["linux_coreos",    %w[coreos rhcos]],
    ["linux_redhat",    %w[redhat rhel]],
    ["linux_fedora",    %w[fedora]],
    ["linux_gentoo",    %w[gentoo]],
    ["linux_centos",    %w[centos]],
    ["linux_debian",    %w[debian]],
    ["linux_esx",       %w[vmnixx86 vmwareesxserver esxserver vmwareesxi]],
    ["linux_solaris",   %w[solaris]],
    ["linux_oracle",    ["oracle", /^ol/]],
    ["linux_photon",    %w[photon]],
    ["linux_generic",   %w[linux]],
    ["unix_aix",        %w[aix vios]],
    ["ibm_i",           %w[ibmi]],
    ["ibm_power_vm",    %w[phyp]]
  ].freeze

  def self.add_elements(vm, xmlNode)
    add_missing_elements(vm, xmlNode, "system/os")
    add_missing_elements(vm, xmlNode, "system/account_policy")
  end

  def self.add_missing_elements(vm, xmlNode, findPath)
    nh = xml_to_hashes(xmlNode, findPath)
    return if nh.nil?

    nh.delete(:type)
    if vm.operating_system.nil?
      vm.operating_system = new(nh)
    else
      vm.operating_system.update(nh)
    end
  end

  def self.xml_to_hashes(xmlNode, findPath)
    el = XmlFind.findElement(findPath, xmlNode.root)
    return nil unless MiqXml.isXmlElement?(el)

    nh = el.attributes.to_h
    if findPath == "system/os"
      nh[:name] = nh.delete(:machine_name)
      nh[:bitness] = nh.delete(:architecture)
      nh[:build_number] = nh.delete(:build)
      nh[:system_type] = system_type(nh[:product_type])
    end
    nh
  end

  def self.system_type(value)
    case value.to_s.downcase
    when "servernt" then "server"
    when "winnt" then "desktop"
    else "unknown"
    end
  end

  def self.normalize_os_name(os_name)
    clean_os_name = os_name.downcase.gsub(/[^a-z0-9]/, "")
    OS_MAP.each do |normalized_name, candidate_names|
      candidate_names.each do |candidate|
        if (candidate.kind_of?(String) && clean_os_name.include?(candidate)) ||
           (candidate.kind_of?(Regexp) && clean_os_name.match?(candidate))
          return normalized_name
        end
      end
    end
    "unknown"
  end

  def self.image_name(obj)
    os_name = nil

    # Select most accurate name field
    os = obj.operating_system
    if os
      # check the given field names for possible matching value
      os_name = [:distribution, :product_type, :product_name].each do |field|
        os_field = os.send(field)
        break(os_field) if os_field && normalize_os_name(os_field) != "unknown"
      end

      # If the normalized name comes back as unknown, nil out the value so we can get it from another field
      if os_name.kind_of?(String)
        os_name = nil if normalize_os_name(os_name) == "unknown"
      else
        os_name = nil
      end
    end

    # If the OS Name is still blank check the 'user_assigned_os'
    if os_name.nil? && obj.respond_to?(:user_assigned_os) && obj.user_assigned_os
      os_name = obj.user_assigned_os
    end

    # If the OS Name is still blank check the hardware table
    if os_name.nil? && obj.hardware && !obj.hardware.guest_os.nil?
      os_name = obj.hardware.guest_os
      # if we get generic linux or unknown back see if the vm name is better
      norm_os = normalize_os_name(os_name)
      if ["linux_generic", "unknown"].include?(norm_os)
        vm_name = normalize_os_name(obj.name)
        return vm_name unless vm_name == "unknown"
      end
    end

    # If the OS Name is still blank use the name field from the object given
    os_name = obj.name if os_name.nil?

    # Normalize name to match existing icons
    normalize_os_name(os_name)
  end

  def self.platform(obj)
    image_name(obj).split("_").first
  end
end
