module NetworkSearchable
  extend ActiveSupport::Concern

  included do
    include Elasticsearch::Model
    include Elasticsearch::Model::Callbacks

    index_name "location_networks_#{Rails.env}"
    
    # Callbacks for elk reindexing
    after_update :reindex_if_timezone_changed
    after_save :update_elasticsearch_index
    after_destroy :delete_from_elasticsearch_index, :reindex_affected_users

    # for router inventories
    after_touch :update_elasticsearch_index
    after_save :reindex_related_inventories
    after_destroy :reindex_related_inventories

    settings index: {
      number_of_shards: 1,
      analysis: {
        normalizer: {
          case_insensitive_normalizer: {
            type: "custom",
            filter: ["lowercase"]
          }
        }
    }
    } do
      mappings dynamic: 'false' do
        indexes :network_name, type: 'text' do # For searching "Main Office Network"
          indexes :keyword, type: 'keyword'    # For exact match/sorting like filter by "Main Office Network"
          indexes :sort, type: 'keyword', normalizer: 'case_insensitive_normalizer'
        end
        
        indexes :timezone, type: 'text' do
          indexes :keyword, type: 'keyword'
          indexes :sort, type: 'keyword', normalizer: 'case_insensitive_normalizer'
        end
        
        indexes :tz_with_offset, type: 'text' do
          indexes :keyword, type: 'keyword'
        end
        
        indexes :router_inventories_count, type: 'integer'
        indexes :tags, type: 'keyword'
        
        indexes :tags_keyword, type: 'text' do
          indexes :keyword, type: 'keyword'
          indexes :sort, type: 'keyword', normalizer: 'case_insensitive_normalizer'
        end

        indexes :external_guid, type: 'text' do
          indexes :keyword, type: 'keyword'
          indexes :sort, type: 'keyword', normalizer: 'case_insensitive_normalizer'
        end

        indexes :cloned_network_name, type: 'text' do
          indexes :keyword, type: 'keyword'
          indexes :sort, type: 'keyword', normalizer: 'case_insensitive_normalizer'
        end

        indexes :organisation_id, type: 'integer'
        indexes :cloned_network_id, type: 'integer'
        indexes :original_cloned_network_id, type: 'integer'
        indexes :network_template, type: 'boolean'
        indexes :template_adherence, type: 'boolean'
        indexes :enable_policy, type: 'boolean'
        indexes :tag_list, type: 'keyword'
        
        indexes :created_at, type: 'date'
        indexes :updated_at, type: 'date'
      end
    end

    def as_indexed_json(_options = {})
      json = as_json(
        only: [
          :id,
          :network_name,
          :timezone,
          :organisation_id,
          :cloned_network_id,
          :original_cloned_network_id,
          :network_template,
          :template_adherence,
          :enable_policy,
          :created_at,
          :updated_at
        ]
      )
      
      json.merge!('tz_with_offset' => self.tz_with_offset)
      json.merge!('router_inventories_count' => self.router_inventories_count)
      
      tag_names = self.tags.pluck(:name)
      json.merge!('tags' => tag_names)
      json.merge!('tags_keyword' => tag_names.join(" "))
      json.merge!('tag_list' => self.tag_list)
      json.merge!('external_guid' => self.external_guid.present? ? self.external_guid : "") 

      cloned_name = self.cloned_network_id.present? ? LocationNetwork.where(id: cloned_network_id).pluck(:network_name).first : nil
      json.merge!('cloned_network_name' => cloned_name)
      
      json
    end

    def self.count_search_results(query, organisation_id = nil, permitted_network_ids = nil, network_template = nil, network_id = nil, network_name = nil, selector_mode = false)

      if permitted_network_ids.is_a?(Array) && permitted_network_ids.empty?
        return 0
      end

      must_filters = []
      
      #network_id/network_name filter
      if network_id.present?
        must_filters << { term: { _id: network_id.to_s } }
      elsif network_name.present?
        must_filters << { term: { "network_name.keyword" => network_name } }
      end
      
      #default filters
      must_filters << { term: { network_template: network_template } } if network_template.present?
      must_filters << { term: { organisation_id: organisation_id } } if organisation_id.present?
      
      #permitted networks filter
      if permitted_network_ids.is_a?(Array) && permitted_network_ids.any?
        must_filters << { ids: { values: permitted_network_ids.map(&:to_s) } }
      end
      
      should_conditions = []
      raw_query = query.to_s.strip.downcase
      
      if (network_id.blank? && network_name.blank?) && raw_query.present?
        searchable_fields = if selector_mode
                              { network_name: { boost_values: { base: 20, phrase: 16, wildcard: 12 } } }
                            else
                              {
                                network_name: { boost_values: { base: 20, phrase: 16, wildcard: 12 } },
                                timezone: { boost_values: { base: 10, phrase: 8, wildcard: 6 } },
                                tz_with_offset: { boost_values: { base: 12, phrase: 10, wildcard: 8 } },
                                tags_keyword: { boost_values: { base: 15, phrase: 12, wildcard: 9 } },
                                external_guid: { boost_values: { base: 12, phrase: 10, wildcard: 8 } } 
                              }
                            end

        special_chars = ['_', '@', '.', '/', '\\', '+', '(', ')', '#',  "'", '"', '!', '$', '*', '&', ',']
        has_special_chars = special_chars.any? { |char| raw_query.include?(char) }
        has_hyphen = raw_query.include?("-")
        has_multiple_words = raw_query.include?(" ") && !has_special_chars
        has_colon = raw_query.include?(":")

        searchable_fields.each do |field, config|
          field_str = field.to_s

          if has_colon || has_hyphen
            should_conditions += [
              { term: { "#{field_str}.keyword" => { value: raw_query, boost: config[:boost_values][:base] * 3.0 } } },
              { wildcard: { "#{field_str}.keyword" => { value: "*#{raw_query}*", case_insensitive: true, boost: config[:boost_values][:wildcard] * 2.5 } } },
            ]
          elsif has_special_chars
            escaped_query = raw_query.gsub('*', '\\*')
            should_conditions += [
              { term: { "#{field_str}.keyword" => { value: escaped_query, boost: config[:boost_values][:base] * 1.2 } } },
              { wildcard: { "#{field_str}.keyword" => { value: "*#{escaped_query}*", boost: config[:boost_values][:wildcard] * 1.5, case_insensitive: true } } }         
            ]
          elsif has_multiple_words
            should_conditions += [
              { match_phrase: { field_str => { query: raw_query, boost: config[:boost_values][:phrase] * 1.5 } } },
              { match_phrase_prefix: { field_str => { query: raw_query, boost: config[:boost_values][:phrase] } } },
              { wildcard: { "#{field_str}.keyword" => { value: "*#{raw_query}*", boost: config[:boost_values][:wildcard] * 1.2, case_insensitive: true } } },
              { term: { "#{field_str}.keyword" => { value: raw_query, boost: config[:boost_values][:base] * 1.5 } } }
            ]
          else
            should_conditions += [
              { term: { "#{field_str}.keyword" => { value: raw_query, boost: config[:boost_values][:base] * 2.0 } } },
              { wildcard: { "#{field_str}.keyword" => { value: "*#{raw_query}*", boost: config[:boost_values][:wildcard] * 1.8, case_insensitive: true } } }
            ]
          end
        end
      end
      
      #final query
      query_definition = if raw_query.present? && network_id.blank? && network_name.blank? && should_conditions.any?
        {
          bool: {
            must: must_filters,
            should: should_conditions,
            minimum_should_match: 1
          }
        }
      else
        {
          bool: {
            must: must_filters
          }
        }
      end
      
      #count
      count_definition = {
        query: query_definition,
        track_total_hits: true
      }
      
      result = self.__elasticsearch__.search(count_definition)
      result.results.total
    end

    def self.search_networks(query, sort_column, sort_direction, organisation_id = nil, per_page = 10, selector_mode = false, network_template = nil, permitted_network_ids = nil, network_id = nil, network_name = nil, search_after = nil)

      if permitted_network_ids.is_a?(Array) && permitted_network_ids.empty?
        mock_response = OpenStruct.new(
            results: [],
            has_more?: false,
            next_search_after: nil
          )
          return mock_response
      end
      
      start_time = Time.now
      Rails.logger.info("Network Pagination Debug - search_after received: #{search_after.inspect}")
      
      # Normalize sort field and create sort definition
      sort_field = case sort_column
                   when "network_name" then 'network_name'
                   when "timezone_with_offset" then 'timezone'
                   when "devices_count" then 'router_inventories_count'
                   when "tag_list" then 'tags_keyword'
                   when "external_guid" then 'external_guid' 
                   when "cloned_network_name" then 'cloned_network_name'
                   when "network_template" then 'network_template'
                   else 'network_name'
                   end
      
      sort_definition = if selector_mode
                         [
                           { "network_name.sort" => { order: 'asc' } },
                           { _id: { order: "asc" } }
                         ]
                       elsif sort_field == 'router_inventories_count'
                         [
                           { router_inventories_count: { order: sort_direction } },
                           { _id: { order: "asc" } }
                         ]
                       elsif sort_field == 'tags_keyword'
                         [
                           { 'tags_keyword.sort' => { order: sort_direction } },
                           { _id: { order: "asc" } }
                         ]
                        elsif sort_field == 'network_template'
                         [
                           { network_template: { order: sort_direction } },
                           { _id: { order: "asc" } }
                         ]
                       else
                         [
                           { "#{sort_field}.sort" => { order: sort_direction } },
                           { _id: { order: "asc" } }
                         ]
                       end
      
      # Build filter conditions
      must_filters = []
      
      #network_id or network_name filter
      if network_id.present?
        must_filters << { term: { _id: network_id.to_s } }
      elsif network_name.present?
        must_filters << { term: { "network_name.keyword" => network_name } }
      end
      
      must_filters << { term: { network_template: network_template } } if network_template.present?
      must_filters << { term: { organisation_id: organisation_id } } if organisation_id.present?
      
      #permitted networks filter
      if permitted_network_ids.is_a?(Array) && permitted_network_ids.any?
        must_filters << { ids: { values: permitted_network_ids.map(&:to_s) } }
      end
 
      should_conditions = []
      raw_query = query.to_s.strip.downcase

      if (network_id.blank? && network_name.blank?) && raw_query.present?
        searchable_fields = if selector_mode
                              { network_name: { boost_values: { base: 20, phrase: 16, wildcard: 12 } } }
                            else
                              {
                                network_name: { boost_values: { base: 20, phrase: 16, wildcard: 12 } },
                                timezone: { boost_values: { base: 10, phrase: 8, wildcard: 6 } },
                                tz_with_offset: { boost_values: { base: 12, phrase: 10, wildcard: 8 } },
                                tags_keyword: { boost_values: { base: 15, phrase: 12, wildcard: 9 } },
                                external_guid: { boost_values: { base: 12, phrase: 10, wildcard: 8 } } 
                              }
                            end

        special_chars = ['_', '@', '.', '/', '\\', '+', '(', ')', '#',  "'", '"', '!', '$', '*', '&', ',']
        has_special_chars = special_chars.any? { |char| raw_query.include?(char) }
        has_hyphen = raw_query.include?("-")
        has_multiple_words = raw_query.include?(" ") && !has_special_chars
        has_colon = raw_query.include?(":")

        searchable_fields.each do |field, config|
          field_str = field.to_s

          if has_colon || has_hyphen
            should_conditions += [
              { term: { "#{field_str}.keyword" => { value: raw_query, boost: config[:boost_values][:base] * 3.0 } } },
              { wildcard: { "#{field_str}.keyword" => { value: "*#{raw_query}*", case_insensitive: true, boost: config[:boost_values][:wildcard] * 2.5 } } },
            ]
          elsif has_special_chars
            escaped_query = raw_query.gsub('*', '\\*')
            should_conditions += [
              { term: { "#{field_str}.keyword" => { value: escaped_query, boost: config[:boost_values][:base] * 1.2 } } },
              { wildcard: { "#{field_str}.keyword" => { value: "*#{escaped_query}*", boost: config[:boost_values][:wildcard] * 1.5, case_insensitive: true } } }         
            ]
          elsif has_multiple_words
            should_conditions += [
              { match_phrase: { field_str => { query: raw_query, boost: config[:boost_values][:phrase] * 1.5 } } },
              { match_phrase_prefix: { field_str => { query: raw_query, boost: config[:boost_values][:phrase] } } },
              { wildcard: { "#{field_str}.keyword" => { value: "*#{raw_query}*", boost: config[:boost_values][:wildcard] * 1.2, case_insensitive: true } } },
              { term: { "#{field_str}.keyword" => { value: raw_query, boost: config[:boost_values][:base] * 1.5 } } }
            ]
          else
            should_conditions += [
              { term: { "#{field_str}.keyword" => { value: raw_query, boost: config[:boost_values][:base] * 2.0 } } },
              { wildcard: { "#{field_str}.keyword" => { value: "*#{raw_query}*", boost: config[:boost_values][:wildcard] * 1.8, case_insensitive: true } } }
            ]
          end
        end
      end
      
      #final query
      query_definition = if raw_query.present? && network_id.blank? && network_name.blank? && should_conditions.any?
                          {
                            bool: {
                              must: must_filters,
                              should: should_conditions,
                              minimum_should_match: 1
                            }
                          }
                        else
                          {
                            bool: {
                              must: must_filters
                            }
                          }
                        end
      
      highlight_fields = {}
      if raw_query.present? && network_id.blank? && network_name.blank?
        fields_to_highlight = selector_mode ? ["network_name"] : ["network_name", "timezone", "tz_with_offset", "tags_keyword"]
        fields_to_highlight.each do |field|
          highlight_fields[field] = {}
        end
        
        unless selector_mode
          highlight_fields["network_template"] = {}
        end
      end
      
      #search definition
      search_definition = {
        query: query_definition,
        sort: sort_definition,
        size: per_page.to_i
      }
      
      #search_after parameter if provided
      if search_after.present?
        search_definition[:search_after] = search_after
        Rails.logger.info("Network Pagination Debug - Using search_after: #{search_after}")
      end
      
      #highlighting if needed
      if highlight_fields.any?
        search_definition[:highlight] = {
          pre_tags: ['<strong>'],
          post_tags: ['</strong>'],
          fields: highlight_fields
        }
      end
      
      result = __elasticsearch__.search(search_definition)
      
      end_time = Time.now
      search_time_ms = ((end_time - start_time) * 1000).round(2)
      Rails.logger.info("Network search completed in #{search_time_ms}ms - Query: '#{raw_query}' - Results: #{result.results.size}")
      
      # Extract sort values from the last hit for the next search_after
      if result.response['hits']['hits'].present?
        last_sort_values = result.response['hits']['hits'].last['sort']
        Rails.logger.info("Network Pagination Debug - Next search_after values: #{last_sort_values.inspect}")
      
        result.define_singleton_method(:next_search_after) do
          last_sort_values
        end
      
        has_more = result.results.size >= per_page.to_i
        result.define_singleton_method(:has_more?) do
          has_more
        end
        Rails.logger.info("Network Pagination Debug - result.next_search_after: #{result.next_search_after.inspect}")
        Rails.logger.info("Network Pagination Debug - result.has_more?: #{result.has_more?.inspect}")
      end
      
      return result
    end
  end

  def reindex_if_timezone_changed
    if previous_changes.include?(:timezone)
      __elasticsearch__.index_document
    end
  end

  def update_elasticsearch_index
    reload if persisted? && !changed?
    __elasticsearch__.index_document if persisted?
  rescue => e
    Rails.logger.error "Elasticsearch indexing error for LocationNetwork #{id}: #{e.message}"
  end

    
  def delete_from_elasticsearch_index
    __elasticsearch__.delete_document
  rescue => e
    Rails.logger.error "Elasticsearch delete error for LocationNetwork #{id}: #{e.message}"
  end

  def reindex_affected_users
    network_reference = "network:#{self.id}"
    
    # Find all users in the same organization that have this network in their network_tags
    affected_users = User.joins(:organisation).where(organisation_id: self.organisation_id).where("network_tags LIKE ?", "%#{network_reference}%")
    
    affected_users.find_each do |user|
      begin
        # The user's as_indexed_json method will automatically recalculate
        # network_access_values which will exclude the deleted network
        user.__elasticsearch__.index_document
      rescue => e
        Rails.logger.error "Failed to reindex User #{user.id} after network deletion: #{e.message}"
      end
    end
  end

  def reindex_related_inventories
      router_inventories.find_each do |inventory|
        begin
          inventory.__elasticsearch__.index_document
        rescue => e
          Rails.logger.error "Failed to reindex RouterInventory #{inventory.id}: #{e.message}"
        end
      end
  end

end