# == Schema Information
#
# Table name: location_networks
#
#  id                    :integer          not null, primary key
#  network_name          :string(255)
#  network_type          :string(255)
#  network_configuration :string(255)
#  cloned_network_id     :integer
#  created_at            :datetime
#  updated_at            :datetime
#  organisation_id       :integer
#  timezone              :string(255)
#  aggregation_flag      :boolean
#  hb_freq               :integer          default(5)
#

class LocationNetwork < ActiveRecord::Base
  resourcify
  include RedisWrapper
  include PublicActivity::Model

  acts_as_tagger

  tracked owner: ->(controller, model) { controller && controller.tracked_current_user },params: { :attributes => proc {|controller, model_instance| { "location_network(#{model_instance.network_name})" => model_instance.changes}}},organisation_id: ->(controller, model) { (controller && controller.tracked_current_user) ? controller.tracked_current_user.organisation_id : model.organisation_id }, :location_network_id => proc {|controller, model_instance| model_instance.id }

  tracked assumed_by: proc {|controller, model| controller.user_assumed_by if controller}

  cattr_accessor :current
  attr_accessor :model_current_user, :skip_sync
  # to find the child_networks utilize this scope
  scope :child_networks, ->(location_network_id) { where(id: location_network_id).where.not(cloned_network_id: nil).where(network_template: false) }

  # to find the parent_networks utilize this scope
  scope :parent_networks, ->(cloned_network_id) { where(id: cloned_network_id).where(network_template: true) }
  acts_as_taggable_on :tags
  serialize :vendor_details, Hash

  has_one :template_policy, dependent: :destroy
  accepts_nested_attributes_for :template_policy
  
  DEFAULT_TIMEZONE = 'UTC'
  HW_CATEGORY_MAP = {'router' => 'All Routers', 'access_point' => 'All Access Points'}
  before_validation :set_default_timezone
  before_destroy :check_for_cloned_children
  validates :network_name, presence: true, :uniqueness => {:scope => :organisation_id}
  validates :timezone, presence: true
  validate :timezone_exists
  validate :template_adherence_for_child_only
  validate :template_policy_for_parent_only, if: -> { template_policy.present? }
  validates :network_name, presence: true, :uniqueness => {:scope => :organisation_id}, :format => { :with => /\A[A-Za-z0-9-\/\\+()_@#:'"!$*&.,\s]+\z/ }
  belongs_to :organisation
  belongs_to :integration_type
  has_many :unities, as: :locatable, :dependent => :destroy
  has_many :users, through: :unities
  has_many :roles, through: :unities
  has_many :router_inventories
  has_many :network_ssids, :dependent => :destroy
  has_many :wired_configs, :dependent => :destroy
  has_many :firewall_configs, :dependent => :destroy
  has_many :radio_profiles, :dependent => :destroy
  has_many :switch_configurations, :dependent => :destroy

  has_many :portforwarding_groups
  has_many :acl_groups
  has_one :snmp_config
  has_one :sqm
  has_many :alerts, :dependent => :destroy
  has_many :v_lans
  has_one :ntp_server, :dependent => :destroy
  has_many :loggings, :dependent => :destroy
  has_many :vpn_configs, :dependent => :destroy
  has_many :static_routes, :dependent => :destroy
  #accepts_nested_attributes_for :router_inventories

  has_many :reports, :dependent => :destroy
  has_many :scheduled_reports, :dependent => :destroy
  has_many :config_mappings, as: :resourceable, :dependent => :destroy
  # has_many :associated_radio_profiles, through: :config_mappings, source: :configurable, source_type: "RadioProfile"
  has_one :wired_config, as: :resourceable, :class_name => 'ConfigMapping'
  has_one :associated_wired_config, through: :wired_config, source: :configurable, source_type: "WiredConfig"
  has_one :radio_profile, as: :resourceable, :class_name => 'ConfigMapping'
  has_one :associated_radio_profile, through: :radio_profile, source: :configurable, source_type: "RadioProfile"
  has_many :associated_portforwarding_groups, through: :config_mappings, source: :configurable, source_type: "PortforwardingGroup"
  has_many :associated_acl_groups, through: :config_mappings, source: :configurable, source_type: "AclGroup"
  has_many :associated_snmp_configurations, through: :config_mappings, source: :configurable, source_type: "SnmpConfig"
  has_many :associated_sqms, through: :config_mappings, source: :configurable, source_type: "Sqm"
  has_many :associated_firewall_configs, through: :config_mappings, source: :configurable, source_type: "FirewallConfig"
  has_many :associated_loggings, through: :config_mappings, source: :configurable, source_type: "Logging"
  has_many :associated_vpn_configs, through: :config_mappings ,source: :configurable, source_type: "VpnConfig"
  has_many :associated_static_routes, through: :config_mappings ,source: :configurable, source_type: "StaticRoute"
  has_many :associated_network_ssids, through: :config_mappings, source: :configurable, source_type: "NetworkSsid"
  has_many :wlan_groups, :dependent => :destroy
  has_many :ap_groups, :dependent => :destroy
  has_many :associated_switch_configurations, through: :config_mappings, source: :configurable, source_type: "SwitchConfiguration"
  serialize :vendor_details, Hash
  
  before_create do |ln|
    ln.hb_freq = 60 if ln.vendor_type == "4" || ln.vendor_type == "6"
    ln.hb_freq = 360 if ln.vendor_type == "3"
    ln.hb_freq = 180 if ln.vendor_type == "5"
  end

  after_create do |network|
    #NOTE: Dont create a default configuration for Meraki network.
    if network.vendor_type == "3"
      Meraki::Platform::ConfigurationService.new(network.reload).create_organization_network unless network.vendor_network_id.present?
      #TODO: Create a activity log
    elsif network.vendor_type == "5"
      if !skip_sync
        Ruckus::ConfigurationService.new(network.reload).create_zone unless network.vendor_network_id.present?
        # sync_default wlan group
        network.sync_default_wlan_group
        #TODO: sync_default ap group
        network.default_configuration if network.network_configuration.blank? || "default".eql?(network.network_configuration)
      end
    else
      network.default_configuration if network.network_configuration.blank? || "default".eql?(network.network_configuration)
      network.clone_network_configuration if !network.network_configuration.blank? && "clone".eql?(network.network_configuration)
    end
  end

  after_save :set_redis_key
  after_save :check_for_timezone_changes

  after_save do |x|
    network_ssids = x.network_ssids
    unless network_ssids.blank?
      a =[]; network_ssids.each{|y| a << "ns#{y.id}"}
      redis_set "ln#{x.id}", a.to_json
    end
    if x.vendor_type == "3"
      int_type = x.organisation.integration_types.where(vendor_name: 'meraki').last
      $redis.hset "MERAKI:LN:MAP", "#{x.id}", "#{int_type.org_id}|#{int_type.api_key.strip}|#{x.vendor_network_id}" unless int_type.blank?
    end
  end

  after_update do |x|
    if self.vendor_type == "3"
      res = Meraki::Platform::ConfigurationService.new(x).update_network
    elsif self.vendor_type == "5"
      res = Ruckus::ConfigurationService.new(x).update_zone if !x.skip_sync
    end
  end

  after_destroy :redis_cleanup

  after_destroy do |x|
    if self.vendor_type == "3"
      Meraki::Platform::ConfigurationService.new(x).delete_network
    elsif self.vendor_type == "5"
      Ruckus::ConfigurationService.new(x).delete_zone
      # elsif self.vendor_type == "2"
      #   grp_name = self.vendor_network_id
      #   ArubaApiWrapper::Aruba.new(org_id).delete_network(grp_name)
    end

  end

  def skip_sync
    @skip_sync || false
  end

  def default_configuration
    self.network_ssids.build({ssid_name: "#{self.network_name} Wireless", ssid_mode: "0", nat: 1, is_enabled: true, is_hidden: false, is_isolate: false, security_mode: "none", wpa_algorithm: "tkip", key_renewal_interval: "86400", wpa_key: "12345678",:ip_address=>"192.168.222.1",:captive_portal=> "0", :wallgarden=>"disable", :dhcp_range_start=> "192.168.222.2", :dhcp_range_end=> "192.168.222.254", :dhcp_relay_server=> 0, :dhcp_lease_time=> "86400", :subnet_mask=> "255.255.255.0", :enable_ssid_qos => false, :uplink_priority => "1", default_interim_time: "300", default_idle_timeout: "1800",acl_mac: "0", wna: "1", associated_aps_and_tags: ["network:#{self.id}"]})
    self.save!
  end

  def json_build device=false
    ln = {id: self.id, network_name: self.network_name, cloned_network_id: self.cloned_network_id, timezone: self.timezone, network_template: self.network_template, template_adherence: self.template_adherence, enable_policy: self.enable_policy, tag_list: self.tag_list} 
    ln[:device] = router_inventories.map(&:json_build) unless device.blank?
    
    ln
  end

  def timezone_exists
    return if timezone.present? && ActiveSupport::TimeZone[timezone].present?
    errors.add(:timezone, "is invalid")
  end

  def template_policy_for_parent_only
    if !network_template? && cloned_network_id.present? 
      errors.add(:template_policy, "can only be assigned to template networks")
    end
  end

  def template_adherence_for_child_only
    if network_template? && template_adherence_changed?
      errors.add(:template_adherence, "cannot be updated for template networks")
    end
  end

  def template_child?
    cloned_network_id.present? && !network_template
  end

  def template?
    cloned_network_id.nil? && network_template
  end


  def set_default_timezone
    if self.timezone.blank?
      self.timezone = "Pacific Time (US & Canada)"
      return true
    end
  end

  def clone_network_ssids(cloned_network, ssid_configuration = nil)
    excluded_attributes_ssid = ["id", "created_at", "updated_at", "location_network_id"]
    excluded_attributes_vlan = ["id", "created_at", "updated_at", "location_network_id", "network_ssid_id", "ip_type"]


    if ssid_configuration.present?
      ssid_configuration.each do |config|
        ssid_data = config.slice(*NetworkSsid.attribute_names).except(*excluded_attributes_ssid)
        if ssid_data["v_lan_attributes"].present?
          ssid_data["v_lan_attributes"]= ssid_data["v_lan_attributes"].slice(*VLan.attribute_names).except(*excluded_attributes_vlan)
        end
        new_network_ssid = NetworkSsid.new(ssid_data)
        new_network_ssid.associated_aps_and_tags = ["network:#{self.id}"]

        self.network_ssids << new_network_ssid
      end
    else
      cloned_network.network_ssids.each do |ssid|
        ssid_data = ssid.attributes.except(*excluded_attributes_ssid).merge(clone_id: ssid.id)
        new_network_ssid = NetworkSsid.new(ssid_data)

        assco_arr = Array(ssid.associated_aps_and_tags)
        assco_arr.delete_if { |d| d.match(/AP/) || d.match(/network/) }
        assco_arr << "network:#{self.id}" if Array(ssid.associated_aps_and_tags).any? { |d| d.match(/AP/) || d.match(/network/) }
        new_network_ssid.associated_aps_and_tags = assco_arr
        new_network_ssid.is_isolate = true if ssid.location_network.organisation_id == 201 # NOTE: To be removed once fix the issue

        if ssid.v_lan.present?
          new_v_lan_attributes = ssid.v_lan.attributes.except(*excluded_attributes_vlan).merge(clone_id: ssid.v_lan.id)
          new_network_ssid.build_v_lan(new_v_lan_attributes)
        end

        self.network_ssids << new_network_ssid
      end
    end
  end

  def check_for_cloned_children
    if self.network_template?
      child_network = LocationNetwork.where(cloned_network_id: self.id)
      if child_network.exists? && self.template_adherence?
        errors.add(:base, "Cannot delete this network because it has child networks.")
        return false
      end
    end
    true
  end

  def clone_wired_configs(cloned_network)
    cloned_network.wired_configs.each do |wired_config|
      new_wired_config = WiredConfig.new(wired_config.attributes.except("id","created_at","updated_at","location_network_id").merge(clone_id: wired_config.id))
      new_wired_config.associated_resources = ["network:#{self.id}"]
      wired_config.v_lans.each do |v_lan|
        new_wired_config.v_lans << self.v_lans.map{|a| a if v_lan.v_lan_id == a.v_lan_id}.compact.first if self.v_lans.present?
      end
      self.wired_configs << new_wired_config
    end
  end

  def clone_switch_configs(cloned_network)
    cloned_network.switch_configurations.each do |switch_config|
      new_switch_config = SwitchConfiguration.new(switch_config.attributes.except("id","created_at","updated_at","location_network_id").merge(clone_id: switch_config.id))
      new_switch_config.associated_resources = ["network:#{self.id}"]
      if switch_config.switch_port_configurations.present?
      switch_config.switch_port_configurations.each do |port_config|
          new_switch_port_conf_attributes = port_config.attributes.except("id","created_at","updated_at").merge(clone_id: port_config.id)
        new_switch_config.switch_port_configurations.build(new_switch_port_conf_attributes)
      end
      end
      self.switch_configurations << new_switch_config
    end
  end

  def clone_v_lans(cloned_network)
    v_lans_copy = cloned_network.v_lans.map{|v_lan| v_lan.attributes.except("id","created_at","updated_at","location_network_id","network_ssid_id","ip_type").merge(clone_id: v_lan.id)}
    self.v_lans.build(v_lans_copy) if v_lans_copy.present?
    self.save
  end

  def clone_alerts(cloned_network)
    cloned_network.alerts.each do |alert|
      new_alert = Alert.new(alert.attributes.except("id","created_at","updated_at","location_network_id").merge(clone_id: alert.id))
      alert_rules = alert.alert_rules.map{|alert_rule| alert_rule.attributes.except("id","created_at","updated_at","alert_id").merge(clone_id: alert_rule.id)}
      new_alert.alert_rules.build(alert_rules) if alert_rules.present?
      alert.notification_groups.each do |notification_group|
        organisation = self.organisation
        exist_notification_group = organisation.notification_groups.where(name: notification_group.name).first
        if exist_notification_group.blank?
          new_notification_group = NotificationGroup.new(notification_group.attributes.except("id","created_at","updated_at","organisation_id","email").merge(clone_id: notification_group.id))
          new_notification_group.organisation_id = organisation.id
          new_notification_group.email = self.model_current_user.try(:email)
          new_notification_group.save
        new_alert.notification_groups << new_notification_group
        else
          new_alert.notification_groups << exist_notification_group
        end

      end
      self.alerts << new_alert
    end
  end

  def clone_loggings(cloned_network)
    new_loggings = cloned_network.loggings.map{|logging| logging.attributes.except("id","created_at","updated_at","location_network_id").merge(clone_id: logging.id)}
    self.loggings.build(new_loggings) if new_loggings.present?
    # self.save
  end

  def clone_network_configuration
    cloned_network = self.organisation.location_networks.where(:id => self.cloned_network_id).last
    if cloned_network
      clone_v_lans(cloned_network)
      clone_network_ssids(cloned_network)
      clone_wired_configs(cloned_network)
      clone_alerts(cloned_network)
      clone_switch_configs(cloned_network)
      clone_loggings(cloned_network)
    end
    self.save!
  end

  def rollback_clone_v_lans
    parent_network = LocationNetwork.find_by(id: self.cloned_network_id)
    parent_vlans = parent_network.v_lans
  
    parent_vlans.each do |parent_vlan|
      child_vlan = self.v_lans.find_by(clone_id: parent_vlan.id)
  
      if child_vlan
        attributes_to_update = parent_vlan.attributes.except(
          "id", "created_at", "updated_at", "location_network_id", "network_ssid_id", "ip_type", "clone_id"
        )
        child_vlan.update(attributes_to_update)
      # TODO: pending
      end
    end
  end
  

  def rollback_clone_network_ssids
    parent_network = LocationNetwork.find_by(id: self.cloned_network_id)
    parent_network_ssids = parent_network.network_ssids
  
    parent_network_ssids.each do |parent_ssid|
      child_ssid = self.network_ssids.find_by(clone_id: parent_ssid.id)
  
      if child_ssid
        ssid_attributes_to_update = parent_ssid.attributes.except("id","created_at","updated_at","location_network_id","clone_id")
        child_ssid.update(ssid_attributes_to_update)
         assco_arr = parent_ssid.associated_aps_and_tags
         assco_arr.delete_if {|d| d.match(/AP/) || d.match(/network/)}
         assco_arr << "network:#{self.id}" if parent_ssid.associated_aps_and_tags.any?{|d| d.match(/AP/) || d.match(/network/)}
         child_ssid.associated_aps_and_tags = assco_arr
         child_ssid.is_isolate = true if parent_ssid.location_network.organisation_id == 201 
         
  
        if parent_ssid.v_lan.present? 
           child_v_lan_attributes = parent_ssid.v_lan.attributes.except("id","created_at","updated_at","location_network_id","network_ssid_id","ip_type","clone_id")
           child_ssid.v_lan.update(child_v_lan_attributes)
        end
        child_ssid.save
      # TODO: pending
      end
    end
  end

  def rollback_clone_wired_configs
    parent_network = LocationNetwork.find_by(id: self.cloned_network_id)
    parent_wired_configs = parent_network.wired_configs
  
    parent_wired_configs.each do |parent_wired_config|
      child_wired_config = self.wired_configs.find_by(clone_id: parent_wired_config.id)
  
      if child_wired_config
        attributes_to_update = parent_wired_config.attributes.except(
          "id", "created_at", "updated_at", "location_network_id", "clone_id"
        )
        child_wired_config.update(attributes_to_update)
  
        child_wired_config.v_lans.clear
        parent_wired_config.v_lans.each do |parent_vlan|
          corresponding_child_vlan = self.v_lans.find_by(clone_id: parent_vlan.id)
          child_wired_config.v_lans << corresponding_child_vlan if corresponding_child_vlan
        end
  
        child_wired_config.associated_resources = parent_wired_config.associated_resources
        child_wired_config.save
      # TODO: pending
      end
    end
  end
  

  def rollback_clone_alerts
    parent_network = LocationNetwork.find_by(id: self.cloned_network_id)
    parent_alerts = parent_network.alerts
  
    parent_alerts.each do |parent_alert|
      child_alert = self.alerts.find_by(clone_id: parent_alert.id)
  
      if child_alert
        alert_attributes_to_update = parent_alert.attributes.except("id", "created_at", "updated_at", "location_network_id",  "clone_id")
        child_alert.update(alert_attributes_to_update)
  
        parent_alert.alert_rules.each do |parent_alert_rule|
          child_alert_rule = child_alert.alert_rules.find_by(clone_id: parent_alert_rule.id)
          if child_alert_rule
            alert_rule_attributes_to_update = parent_alert_rule.attributes.except("id", "created_at", "updated_at", "alert_id", "clone_id")
            child_alert_rule.update(alert_rule_attributes_to_update)
          end
        end
  
        parent_alert.notification_groups.each do |parent_notification_group|
          organisation = self.organisation
          child_notification_group = organisation.notification_groups.find_by(clone_id: parent_notification_group.id)
  
          if child_notification_group
            notification_group_attributes_to_update = parent_notification_group.attributes.except("id", "created_at", "updated_at", "organisation_id", "clone_id")
            child_notification_group.update(notification_group_attributes_to_update)
          end
        end
      # TODO: pending
      end
    end
  end
  

  def rollback_clone_switch_configs
    parent_network = LocationNetwork.find(self.cloned_network_id)
  
    parent_switch_configs = parent_network.switch_configurations
  
    parent_switch_configs.each do |parent_switch_config|
      child_switch_config = self.switch_configurations.find_by(clone_id: parent_switch_config.id)
  
      if child_switch_config
        updated_attributes = parent_switch_config.attributes.except("id", "created_at", "updated_at", "location_network_id", "clone_id")
        child_switch_config.update(updated_attributes)

        parent_port_numbers = parent_switch_config.switch_port_configurations.pluck(:port_number)

        child_switch_config.switch_port_configurations.each do |child_port_config|
          unless parent_port_numbers.include?(child_port_config.port_number)
            child_port_config.destroy
          end
        end
  
        parent_switch_config.switch_port_configurations.each do |parent_port_config|
          child_port_config = child_switch_config.switch_port_configurations.find_by(clone_id: parent_port_config.id)
          if child_port_config
            updated_port_attributes = parent_port_config.attributes.except("id", "created_at", "updated_at", "switch_configuration_id", "clone_id")
            child_port_config.update(updated_port_attributes)
          end
        end
        child_switch_config.associated_resources = parent_switch_config.associated_resources
        child_switch_config.save
      # TODO: pending
      end
    end
  end
  

  def rollback_clone_loggings
    parent_network = LocationNetwork.find_by(id: self.cloned_network_id)
    parent_loggings = parent_network.loggings
  
    parent_loggings.each do |parent_logging|
      child_logging = self.loggings.find_by(clone_id: parent_logging.id)
  
      if child_logging
        attributes_to_update = parent_logging.attributes.except(
          "id", "created_at", "updated_at", "location_network_id", "clone_id"
        )
        child_logging.update(attributes_to_update)
      # TODO: pending
    end
    end
  end

  def clear_and_clone_configs(ssid_configuration, network_template_id = nil)
    self.v_lans.destroy_all
    self.network_ssids.destroy_all
    self.wired_configs.destroy_all
    self.alerts.destroy_all
    self.switch_configurations.destroy_all
    self.loggings.destroy_all
    
    org_networks = self.organisation.location_networks
    parent_network = nil
    if network_template_id.present?
      parent_network = org_networks.parent_networks(network_template_id).last
      self.update_column(:cloned_network_id, network_template_id) unless parent_network.blank?
    else
      parent_network = org_networks.parent_networks(self.cloned_network_id).last
    end
      
    if parent_network
      clone_v_lans(parent_network)
      if ssid_configuration.present?
        clone_network_ssids(parent_network, ssid_configuration)
      else
        clone_network_ssids(parent_network)
      end
      clone_wired_configs(parent_network)
      clone_alerts(parent_network)
      clone_switch_configs(parent_network)
      clone_loggings(parent_network)
      self.save
    else
      raise ActiveRecord::RecordNotFound, "Template network not found"
    end
  end


  def network_throughput_for_interval(router_inventories, from_time, end_time, cal="$sum")
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    throughput = MonitoringDataCapped.collection.aggregate({'$match' => {"info.NASID"=>{"$in"=> router_inventories},
                                                                         "created_at"=>{"$gte"=> from_time, "$lt"=> end_time}}},
                                                           {"$project" => {"uplink" => {"$ifNull" => [ "$uplink", "$lan" ]}}},
                                                           {"$project" => {"uplink.TX_RATE" => 1,
                                                                           "uplink.RX_RATE" => 1
                                                           }},
                                                           {"$unwind" => "$uplink"},
                                                           {"$group" => {"_id" => nil,
                                                                         "tx_rate" => {cal => "$uplink.TX_RATE"},
                                                                         "rx_rate" => {cal => "$uplink.RX_RATE"}}},
                                                           {"$project" =>  {"tx" => "$tx_rate", "rx" => "$rx_rate"}})

    clients = MonitoringDataCapped.collection.aggregate({'$match' => { "info.NASID"=>{"$in"=> router_inventories},
                                                                       "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},
                                                        {"$project" => {"clients" => '$clients'}},
                                                        {"$unwind" => '$clients'},
                                                        {"$group" => {"_id" => "nil",
                                                                      "tx_usage" => {cal => "$clients.TX_RATE"},
                                                                      "rx_usage" => {cal => "$clients.RX_RATE"}}},
                                                        {"$project" => {"tx" => "$tx_usage", "rx" => "$rx_usage"}})
    if clients.blank?
      clients = [{'rx' => 0, 'tx' => 0}]
    end
    if throughput.blank?
      throughput = [{'rx' => 0, 'tx' => 0}]
    end
    {rx_clients: clients[0]['rx'], tx_clients: clients[0]['tx'], tx_throughput: throughput[0]['tx'], rx_throughput: throughput[0]['rx'], from: from_time.to_i, to: end_time.to_i}

  end

  def clients_throughput_for_interval(router_inventories, from_time, end_time, cal="$sum", cli_mac=nil)
    cal = "$sum" if cal.blank?
    pipelines = [ {'$match' => { "info.NASID"=>{"$in"=> router_inventories},
                                 "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},
                  {"$project" => {"clients" => '$clients', "nasid" => "$info.NASID"}},
                  {"$unwind" => '$clients'}]
    pipelines << {"$match" => {"clients.MAC" => cli_mac}} if cli_mac.present?
    pipelines << {"$group" => {"_id" => "$clients.MAC",
                               "tx_usage" => {cal => "$clients.TX_RATE"},
                               "rx_usage" => {cal => "$clients.RX_RATE"},
                               "last_details" => {"$last" => "$clients"},
                               "nasid" => {"$last" => "$nasid"}}}
    # "TYPE"=>{"$last" => "$clients.TYPE"},
    # "SSID_UNIQ_ID"=>{"$last" => "$clients.TYPE"},
    # "SSID"=>{"$last" => "$clients.SSID"},
    # "BD"=>{"$last" => "$clients.TYPE"},
    # "SIGNAL_AVG"=>{"$last" => "$clients.TYPE"},
    # "SIGNAL"=>{"$last" => "$clients.TYPE"},
    # "HOSTNAME"=>{"$last" => "$clients.TYPE"},
    # "DEVICE_TYPE"=>{"$last" => "$clients.DEVICE_TYPE"}}}
    pipelines << {"$project" => {"tx" => "$tx_usage", "rx" => "$rx_usage", "last_details" => 1, "nasid" => 1}}

    clients = MonitoringDataCapped.collection.aggregate(pipelines)
    if clients.present?
      clients = {"tx" => clients.sum {|d| d['tx']}, "rx" => clients.sum {|d| d['rx']}, "clients" => clients.inject({}) {|h,d| h[d['nasid']] ||= []; h[d['nasid']] << d['last_details'];h }}#map {|d| d['last_details']}}
    else
      clients = {'rx' => 0, 'tx' => 0}
    end

    clients
  end

  def uplinks_throughput_for_interval(router_inventories, from_time, end_time, cal="$sum")
    cal = "$sum" if cal.blank?

    pipelines = [ {'$match' => { "info.NASID"=>{"$in"=> router_inventories},
                                 "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},
                  {"$project" => {"uplink" => {"$ifNull" => [ "$uplink", "$lan" ]}, "clients" => 1}},
                  {"$project" => {"uplink.TX_RATE" => 1,
                                  "uplink.RX_RATE" => 1,
                                  "uplink.TYPE" => 1,
                                  "uplink.UID" => 1,
                                  "uplink.UPLINK_IP" => 1,
                                  "clients" => 1}},
                  {"$unwind" => "$uplink"},
                  {"$group" => {"_id" => "$uplink.UID",
                                "tx_rate" => {cal => "$uplink.TX_RATE"},
                                "rx_rate" => {cal => "$uplink.RX_RATE"},
                                "last_details" => {"$last" => "$clients"}}},
                  {"$project" => {"tx" => "$tx_rate", "rx" => "$rx_rate", "last_details" => "$last_details"}} ]

    ap_thro = MonitoringDataCapped.collection.aggregate(pipelines)
    if ap_thro.present?
      uplink_thro = {'uplinks' => {}, 'clients' => []}
      ap_thro.each do |ap|
        uplink_thro['uplinks'][ap['_id']] = {"type" => ap['_id'],"tx" => ap['tx'], "rx" => ap['rx']}
        uplink_thro['clients'] = ap['last_details']
      end
    else
      uplink_thro = {'uplinks' => {}, 'clients' => []}
    end

    uplink_thro
  end

  def top_aps_usage(router_inventories, from_time, end_time, param="BYTES_INT")
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    MonitoringDataCapped.collection.aggregate({'$match' => { "info.NASID"=>{"$in"=> router_inventories},
                                                             "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},
                                              {"$project" => {"info.NASID" => 1, "uplink" => {"$ifNull" => [ "$uplink", "$lan" ]}}},
                                              {"$unwind" => "$uplink"},
                                              {"$group" => {"_id" => "$info.NASID",
                                                            "tx_usage" => {"$sum" => "$uplink.TX_#{param}"},
                                                            "rx_usage" => {"$sum" => "$uplink.RX_#{param}"}}},
                                              {"$project" => {"tx" => "$tx_usage", "rx" => "$rx_usage",
                                                              "total_usage" => {"$add" => ["$tx_usage", "$rx_usage"]}}},
                                              {"$sort" => {"total_usage" => -1}})
  end

  def top_clients_usage(router_inventories, from_time, end_time, param="BYTES_INT")
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    MonitoringDataCapped.collection.aggregate({'$match' => { "info.NASID"=>{"$in"=> router_inventories},
                                                             "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},
                                              {"$project" => {"clients" => '$clients', "info.NASID" => 1}},
                                              {"$unwind" => '$clients'},
                                              {"$group" => {"_id" => "$clients.MAC",
                                                            "tx_usage" => {"$sum" => "$clients.TX_#{param}"},
                                                            "rx_usage" => {"$sum" => "$clients.RX_#{param}"},
                                                            "aps" => {"$addToSet" => "$info.NASID"},
                                                            "device" => {"$last" => "$clients.DEVICE_TYPE"}}},
                                              {"$project" => {"tx" => "$tx_usage",
                                                              "rx" => "$rx_usage",
                                                              "total_usage" => {"$add" => ["$tx_usage", "$rx_usage"]},
                                                              "ap_mac" => "$aps",
                                                              "device" => { "$ifNull" => [ "$device", 0 ] }}},
                                              {"$sort" => {"total_usage" => -1}})
  end

  # raj added this code(start)
  def usage_per_ssid(router_inventories, from_time, end_time)
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    MonitoringDataCapped.collection.aggregate({'$match' => { "info.NASID"=>{"$in"=> router_inventories},"created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}}, {"$project" => {"clients" => '$clients'}}, {"$unwind" => '$clients'}, {"$group" => {"_id" => {"ssid"=>"$clients.SSID_UNIQ_ID","name"=>"$clients.SSID"},"tx_usage" => {"$sum" => "$clients.TX_BYTES_INT"},"rx_usage" => {"$sum" => "$clients.RX_BYTES_INT"}}}, {"$project" => {"tx" => "$tx_usage", "rx" => "$rx_usage","total_usage" => {"$add" => ["$tx_usage", "$rx_usage"]}}},{"$sort" => {"total_usage" => -1}})

    # MonitoringChild.collection.aggregate({'$match' => { "info.NASID"=>{"$in"=> router_inventories},"created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}}, {"$project" => {"clients" => '$clients'}}, {"$unwind" => '$clients'}, {"$group" => {"_id" => "$clients.SSID_UNIQ_ID","tx_usage" => {"$sum" => "$clients.TX_BYTES"},"rx_usage" => {"$sum" => "$clients.RX_BYTES"}}}, {"$project" => {"ssid"=>"$_id","tx" => "$tx_usage", "rx" => "$rx_usage","total_usage" => {"$add" => ["$tx_usage", "$rx_usage"]}}},{"$sort" => {"total_usage" => -1}})
  end

  def no_of_clients_count_per_ssids_dashboard(router_inventories, from_time, end_time)
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    MonitoringDataCapped.collection.aggregate({'$match' => {'info.NASID' => {'$in' => router_inventories}, "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},{"$project" => {"ap_mac" => "$info.NASID", "clients" => '$clients'}}, {"$unwind" => '$clients'},{"$project" => {"clients.SSID_UNIQ_ID" => 1,"clients.SSID"=>1, "clients.MAC" => 1}},{"$group" => {"_id" => {"SSID" => "$clients.SSID_UNIQ_ID", "name"=>"$clients.SSID"},"macs" => {"$addToSet" => "$clients.MAC"}}}, {"$project" => {"cli_count" => {"$size" => "$macs"}}}, {"$sort" => {"cli_count" => -1}}, {"$limit" => 5})
  end

  def ap_geo_loc(router_inventories, from_time, end_time)
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    MonitoringChild.collection.aggregate({'$match' => {'info.NASID' => {'$in' => router_inventories}, "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}}      ,{"$project" => {"ap_mac" => "$info.NASID", "system" => '$s_info'}},{"$group" =>{"_id"=>{"ap_mac" => "$ap_mac"},"public_ip"=>{"$addToSet"=>"$system.PUBLIC_IP"}}})

  end

  def no_of_clients_count_per_month(router_inventories, from_time, end_time,offset)
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    range = (Time.at(end_time).to_date - Time.at(from_time).to_date).to_i

    case
    when range > 366 ; group_by = {"year"=> {"$year"=>"$created_at"}}
    when range > 31 ;  group_by = {"year"=> {"$year"=>"$created_at"},"month"=> {"$month"=>"$created_at"}}
    when range > 8 ;  group_by = {"year"=> {"$year"=>"$created_at"},"month"=> {"$month"=>"$created_at"},"week"=>{"$week"=>"$created_at"}}
    when range > 0 ;  group_by = {"year"=> {"$year"=>"$created_at"},"month"=> {"$month"=>"$created_at"},"day"=>{"$dayOfMonth"=>"$created_at"}}
    end
    group_by.merge!({"SSID" => "$clients.SSID_UNIQ_ID","name"=>"$clients.SSID"})
    MonitoringChild.collection.aggregate({'$match' => {'info.NASID' => {'$in' => router_inventories},'created_at'=>{ "$exists"=> true }, "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},{"$project" => {"ap_mac" => "$info.NASID", "clients" => '$clients',"created_at"=>{"$add" => ["$created_at", offset.to_i]}}}, {"$unwind" => '$clients'},{"$project" => {"clients.SSID_UNIQ_ID" => 1,"clients.SSID" => 1, "clients.MAC" => 1,"created_at"=>1}}, {"$group" => {"_id" => group_by,"macs" => {"$addToSet" => "$clients.MAC"}}}) rescue []
  end

  def ap_versions(router_inventories, from_time, end_time)
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    MonitoringChild.collection.aggregate({'$match' => {'info.NASID' => {'$in' => router_inventories},"created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},{"$group"=>{"_id"=>{"ap_mac"=>"$info.NASID"}, "version"=>{"$addToSet"=>'$info.PIAP_VERSION'}}})
  end
  # raj added this code(end)

  def aps_with_public_ip(router_inventories)
    MonitoringChild.collection.aggregate( {'$match' => {'info.NASID' => {'$in' => router_inventories}}},
                                          {'$sort' => {"created_at" => 1}},
                                          {'$group' => {'_id' => '$info.NASID',
                                                        'public_ip' => {"$last" => "$s_info.PUBLIC_IP"}}} )
  end

  def clients_count(router_inventories)
    clients = MonitoringChild.collection.aggregate( {'$match' => {'info.NASID' => {'$in' => router_inventories}}},
                                                    {"$project" => {"clients" => '$clients'}},
                                                    {"$unwind" => '$clients'},
                                                    {"$group" => {"_id" => "$clients.MAC"}} )
    clients.delete({"_id" => nil})
    clients.count
  end

  def no_of_clients_count_per_ssids_dashboard(router_inventories, from_time, end_time)
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    MonitoringDataCapped.collection.aggregate({'$match' => {'info.NASID' => {'$in' => router_inventories}, "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},{"$project" => {"ap_mac" => "$info.NASID", "clients" => '$clients'}}, {"$unwind" => '$clients'},{"$project" => {"clients.SSID_UNIQ_ID" => 1,"clients.SSID"=>1, "clients.MAC" => 1}},{"$group" => {"_id" => {"SSID" => "$clients.SSID_UNIQ_ID", "name"=>"$clients.SSID"},"macs" => {"$addToSet" => "$clients.MAC"}}}, {"$project" => {"cli_count" => {"$size" => "$macs"}}}, {"$sort" => {"cli_count" => -1}}, {"$limit" => 5})
  end

  def no_of_clients_count_per_ssids(router_inventories, from_time, end_time)
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    # MonitoringChild.collection.aggregate({'$match' => {'info.NASID' => {'$in' => router_inventories}, "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},{"$project" => {"ap_mac" => "$info.NASID", "clients" => '$clients'}}, {"$unwind" => '$clients'},{"$project" => {"clients.SSID_UNIQ_ID" => 1, "clients.MAC" => 1}},{"$group" => {"_id" => {"SSID" => "$clients.SSID_UNIQ_ID"},"macs" => {"$addToSet" => "$clients.MAC"}, "count"=>{"$sum"=>1}}},{"$project" => {"_id" => "$_id.SSID", "count" => "$count"}})

    MonitoringDataCapped.collection.aggregate({'$match' => {'info.NASID' => {'$in' => router_inventories}, "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},{"$project" => {"ap_mac" => "$info.NASID", "clients" => '$clients'}}, {"$unwind" => '$clients'},{"$project" => {"clients.SSID_UNIQ_ID" => 1,"clients.SSID"=>1, "clients.MAC" => 1}},{"$group" => {"_id" => {"SSID" => "$clients.SSID_UNIQ_ID", "name"=>"$clients.SSID"},"macs" => {"$addToSet" => "$clients.MAC"}}})
  end

  def clients_per_aps(router_inventories, from_time, end_time)
    from_time = Time.zone.at from_time
    end_time = Time.zone.at end_time
    MonitoringDataCapped.collection.aggregate({'$match' => {'info.NASID' => {'$in' => router_inventories},
                                                            "created_at"=>{"$gt"=> from_time, "$lte"=> end_time}}},
                                              {"$project" => {"ap_mac" => "$info.NASID", "clients" => '$clients'}},
                                              {"$project" => {"ap_mac" => 1, "clients_mac" => "$clients.MAC"}},
                                              {"$unwind" => "$clients_mac"},
                                              {"$group" => {"_id" => "$ap_mac", "macs" => {"$addToSet" => "$clients_mac"}}})
  end

  def network_unread_notifications
    Notification.where(:ap_mac_id.in => self.router_inventories.map(&:mac_id),:is_read => false).desc("created_at")
  end

  def network_read_notifications
    Notification.where(:ap_mac_id.in => self.router_inventories.map(&:mac_id),:is_read => true).desc("created_at")
  end

  def grouped_collection_of_ap_and_tags
    hash = {}
    hash["Network"] = [[self.network_name, "network:#{self.id}"]]
    hash["Tag"] = self.router_inventories.tag_counts.map {|t| [t.name, "tag:#{t.id}"]}
    hash["Device Inventory"] = self.router_inventories.map {|ri| ["#{ri.name.present? ? (ri.name + " (#{ri.mac_id.last(8)})") : ri.mac_id}", "AP:#{ri.id}"]}
    hash['Device Category'] = HardwareCategory.where.not(name: 'switch').map {|hw| [HW_CATEGORY_MAP[hw.name], "HWC:#{hw.id}"]}
    hash
  end
  def grouped_collection_of_ap
    hash = {}
    #hash["Network"] = [[self.network_name, "network:#{self.id}"]]
    hash["Router Inventory"] = self.router_inventories.map {|ri| ["#{ri.name.present? ? (ri.name + " (#{ri.mac_id.last(8)})") : ri.mac_id}", "AP:#{ri.id}"]}
    hash
  end

  def tz_offset
    Time.zone.now.in_time_zone(self.timezone || DEFAULT_TIMEZONE).utc_offset
  end

  def tz_with_offset
    tz = ActiveSupport::TimeZone.new(self.timezone || DEFAULT_TIMEZONE)
    "(GMT#{tz.formatted_offset(true)}) " + tz.name
  end

  def generate_hourly_aggregate_data ts
    tz = self.timezone.blank? ? DEFAULT_TIMEZONE : self.timezone
    #NOTE:st - start time with network timezone
    st = ts.in_time_zone(tz).beginning_of_hour - 1.hour
    ris = self.router_inventories.pluck(:mac_id)
    unless ris.blank?
      ApAggregateWorker.perform_async st, ris, self.id
      ClientAggregateWorker.perform_async st, ris, self.id
      SsidAggregateWorker.perform_async st, ris, self.id
      UplinkAggregateWorker.perform_async st, ris, self.id
    end
  end

  def self.generate_hourly_aggregate_data
    ts = Time.zone.now.beginning_of_hour
    puts "Aggregate data for Hour: #{ts}"
    lns = self.all
    lns.each do |network|
      network.generate_hourly_aggregate_data ts
    end
  end

  def regenerate_agregate_data sd
    tz = self.timezone.blank? ? DEFAULT_TIMEZONE : self.timezone
    ts = Time.zone.now.beginning_of_hour
    et = ts.in_time_zone(tz).beginning_of_hour - 1.hour
    #NOTE:st - start time with network timezone
    st = ActiveSupport::TimeZone[tz].parse(sd.to_s)
    ris = self.router_inventories.pluck(:mac_id)
    unless ris.blank?
      while st <= et
        ApAggregateWorker.perform_async st, ris, self.id, false
        ClientAggregateWorker.perform_async st, ris, self.id, false, et
        SsidAggregateWorker.perform_async st, ris, self.id, false
        st = st + 1.hour
      end
    end
  end

  def get_configured_vlans
    self.wired_configs.map {|w| w.v_lans }.flatten.uniq#w.get_configured_vlans}.flatten.compact.uniq
  end

  def self.get_all_based_on_tag(id)
    LocationNetwork.tagged_with(id)
  end

  def sync_default_wlan_group
    default_group = self.get_default_wlan_group
    if default_group.blank?
      wlan_groups = Ruckus::ConfigurationService.new(self).get_all_wlan_groups || []
      default_group = wlan_groups.select {|d| d['name'] == 'default'}.first
      if default_group
        self.wlan_groups.create name: 'default', vendor_id: default_group['id']
      end
    end
  end

  def get_default_wlan_group
    self.wlan_groups.where(name: 'default').first
  end

  def get_default_ap_group
    default_wlan_group = self.get_default_wlan_group
    if default_wlan_group
      return default_wlan_group.ap_group#.where(name: "default").first
    else
      return nil
    end
  end

  def is_openwifi?
    self.vendor_type == '4'
  end

  def get_timezone
    zone_tz = self.try(:timezone) || 'UTC'
    zone_tz = 'America/Chicago' if zone_tz == 'CST' 

    zone_tz
  end

  private 

  def check_for_timezone_changes
    if self.timezone_changed? && self.timezone_change.uniq.count > 1
      if self.router_inventories.any?
        unless self.aggregation_flag?
          queue = Sidekiq::Queue.new("aggregate_data")
          queue.each do |job|
            #  if ((job.klass == 'RegenerateAggregateWorker' && job.args[0] == self.id) ||
            #     (job.klass == 'ApAggregateWorker' && job.args[2] == self.id && !job.args[3]) ||
            #     (job.klass == 'ClientAggregateWorker' && job.args[2] == self.id && !job.args[3]) ||
            #     (job.klass == 'SsidAggregateWorker' && job.args[2] == self.id && !job.args[3]))
            #    job.delete
            #  end
            job.delete if job.args[2].to_s == self.id.to_s
          end
          self.update_column(:aggregation_flag, true)
          RegenerateAggregateWorker.perform_async self.id
        end
      end
    end
  end

  def redis_cleanup
    $redis.del "network:#{self.id}"
    Notification.where(:location_network_id => self.id).destroy_all
  end


  def set_redis_key
      $redis.hmset(
        "network:#{self.id}",
        "tz", (self.timezone || DEFAULT_TIMEZONE),
        "presence", self.presence ? 1 : 0,
        "presence_url", self.presence_url || "",
        "org_id", self.organisation_id || 0
      )

      if self.vendor_type == "3"
        meraki = self.organisation.integration_types.where(vendor_name: 'meraki').first
        if meraki && self.vendor_network_id
          $redis.hset("MERAKI:LN:MAP", self.id, "#{meraki.org_id}|#{meraki.api_key}|#{self.vendor_network_id}")
        end
      end

      if (self.presence_changed? || self.presence_url_changed? || self.hb_freq_changed?)
        hb_freq = self.hb_freq
        self.router_inventories.each do |x|
          # Enable the telemetry data for devices under this network
          x.enable_telemetry if self.vendor_type == '4' && self.presence_changed?
          $redis.hset("AP:#{x}", "hb_freq", hb_freq)
          x.update_redis
        end
      end
    end
  end


