module ResourcesSearchable
  extend ActiveSupport::Concern
  
  included do
    include Elasticsearch::Model
    include Elasticsearch::Model::Callbacks
    after_commit :update_in_associated_resources_index, on: [:create, :update]
    after_commit :remove_from_associated_resources_index, on: [:destroy]
  end

  module ClassMethods
    def create_associated_resources_index
      client = Elasticsearch::Model.client
      index_name = "associated_resource_list_#{Rails.env}"
    
      unless client.indices.exists?(index: index_name)
        client.indices.create(
          index: index_name,
          body: {
            settings: {
              number_of_shards: 1
            },
            mappings: {
              properties: {
                rt: { type: 'keyword' },
                rid: { type: 'integer' },
                rn: { type: 'text', fields: { keyword: { type: 'keyword' } } },
                organisation_id: { type: 'integer' },
                # Device specific fields
                ln_id: { type: 'integer' },
                device_mac: { type: 'text', fields: { keyword: { type: 'keyword' } } }
              }
            }
          }
        )
      end
    end
    
    def index_associated_resources
      client = Elasticsearch::Model.client
      index_name = "associated_resource_list_#{Rails.env}"
      
      # Clear existing index
      if client.indices.exists?(index: index_name)
        client.delete_by_query(
          index: index_name,
          body: { query: { match_all: {} } }
        )
      else
        create_associated_resources_index
      end
      

      index_all_networks
      index_all_devices
      
      # Index all tags (device tags and network tags)
      index_all_tags
      
      refresh_associated_resources_index
    end

    def index_all_networks
      LocationNetwork.find_each do |network|
        index_network_as_resource(network)
      end
    end

    def index_all_devices
      RouterInventory.find_each do |device|
        index_device_as_resource(device)
      end
    end

    def index_network_as_resource(network)
      client = Elasticsearch::Model.client
      index_name = "associated_resource_list_#{Rails.env}"
    
      network_doc = {
        rt: 'network',
        rid: network.id,
        rn: network.network_name,
        organisation_id: network.organisation_id
      }
    
      client.index(
        index: index_name,
        id: "network_#{network.id}",
        body: network_doc
      )
    end
    
    def index_device_as_resource(device)
      client = Elasticsearch::Model.client
      index_name = "associated_resource_list_#{Rails.env}"
      
      rn_value = device.name.to_s.strip.empty? ? device.mac_id : device.name
      device_doc = {
        rt: 'device',
        rid: device.id,
        rn: rn_value,
        organisation_id: device.organisation_id,
        ln_id: device.location_network_id,
        device_mac: device.mac_id
      }
    
      client.index(
        index: index_name,
        id: "device_#{device.id}",
        body: device_doc
      )
    end

    def index_all_tags
      client = Elasticsearch::Model.client
      index_name = "associated_resource_list_#{Rails.env}"
      
      #all device tags with their organisation_id in one query
      device_tags = ActsAsTaggableOn::Tag
        .joins("JOIN taggings ON tags.id = taggings.tag_id")
        .joins("JOIN router_inventories ON taggings.taggable_id = router_inventories.id")
        .where(taggings: { taggable_type: 'RouterInventory', context: 'tags' })
        .select('DISTINCT tags.id, tags.name, router_inventories.organisation_id')
      
      #index device tags
      device_tags.find_each do |tag|
        tag_doc = {
          rt: 'tag',
          rid: tag.id,
          rn: tag.name,
          organisation_id: tag.organisation_id
        }
        
        client.index(
          index: index_name,
          id: "device_tag_#{tag.id}",
          body: tag_doc
        )
      end
      
      #all network tags with their organisation_id in one query
      network_tags = ActsAsTaggableOn::Tag
        .joins("JOIN taggings ON tags.id = taggings.tag_id")
        .joins("JOIN location_networks ON taggings.taggable_id = location_networks.id")
        .where(taggings: { taggable_type: 'LocationNetwork', context: 'tags' })
        .select('DISTINCT tags.id, tags.name, location_networks.organisation_id')
      
      # Batch index network tags
      network_tags.find_each do |tag|
        tag_doc = {
          rt: 'network_tag',
          rid: tag.id,
          rn: tag.name,
          organisation_id: tag.organisation_id
        }
        
        client.index(
          index: index_name,
          id: "network_tag_#{tag.id}",
          body: tag_doc
        )
      end
    end

    def refresh_associated_resources_index
      client = Elasticsearch::Model.client
      client.indices.refresh(index: "associated_resource_list_#{Rails.env}")
    end

    def search_associated_resources(organisation_id, search_term = nil, resource_types = ['network', 'device', 'tag','network_tag'], page = 1, per_page = 100, permitted_network_ids = nil, current_network_id = 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
      
      searchable_fields = {
        rn: { boost_values: { base: 20, phrase: 16, wildcard: 12 } },
        device_mac: { boost_values: { base: 15, phrase: 12, wildcard: 9 } }
      }

      raw_query = search_term.to_s.strip.downcase
      client = Elasticsearch::Model.client
      
      all_results = []
      total_count = 0
      
      # Searching each resource type separately to get per_page results for each
      Array(resource_types).each do |resource_type|
        bool_query = {
          must: [
            { term: { organisation_id: organisation_id } },
            { term: { rt: resource_type } }
          ],
          should: [],
          minimum_should_match: 0
        }

        if resource_type == 'device'
          bool_query[:must] << { exists: { field: "ln_id" } }
        end

       
        if permitted_network_ids.is_a?(Array) && permitted_network_ids.any?
          case resource_type
          when 'network'
            bool_query[:must] << { terms: { rid: permitted_network_ids } }

          when 'device'
            bool_query[:must] << { terms: { ln_id: permitted_network_ids } }
          end
        end


        #current_network_id filter (for Events Logging page)
        if current_network_id.present?
          case resource_type
          when 'network'
            # For networks, filter by the network ID itself
            bool_query[:must] << { term: { rid: current_network_id } }
          when 'device'
            # For devices, tags, and network_tags, filter by ln_id (location network id)
            bool_query[:must] << { term: { ln_id: current_network_id } }
          end
        end

        if raw_query.present?
          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?(":")

          should_conditions = []

          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

          bool_query[:should] = should_conditions
          bool_query[:minimum_should_match] = should_conditions.any? ? 1 : 0
        end

        highlight_fields = {}
        searchable_fields.keys.each { |field| highlight_fields[field.to_s] = {} }


        search_definition = {
          index: "associated_resource_list_#{Rails.env}",
          body: {
            _source: ["rt", "rid", "rn", "device_mac"],
            query: { bool: bool_query },
            highlight: {
              pre_tags: ['<strong>'],
              post_tags: ['</strong>'],
              fields: highlight_fields
            },
            sort: [
              { "rn.keyword" => { order: "asc" } },
              { _score: { order: "desc" } }
            ],
            from: (page.to_i - 1) * per_page.to_i,
            size: per_page.to_i,
            track_total_hits: true
          }
        }

        if ['tag', 'network_tag'].include?(resource_type)
          search_definition[:body][:collapse] = {
            field: "rid"  
          }
        end

        response = client.search(search_definition)
        hits = response['hits']['hits']
        resource_total = response['hits']['total']['value']
        total_count += resource_total

        hits.each do |hit|
          all_results << hit['_source']
        end
      end

      {
        total: total_count,
        results: all_results,
        results_per_type: resource_types.map { |rt| 
          { 
            resource_type: rt, 
            count: all_results.count { |r| r['rt'] == rt } 
          } 
        }
      }
    end
  end

  def update_in_associated_resources_index
    client = Elasticsearch::Model.client
    index_name = "associated_resource_list_#{Rails.env}"
    
    if self.is_a?(LocationNetwork)
      # Update network document
      network_doc = {
        rt: 'network',
        rid: id,
        rn: network_name,
        organisation_id: organisation_id
      }
      client.index(index: index_name, id: "network_#{id}", body: network_doc)
      
    elsif self.is_a?(RouterInventory)
      rn_value = name.to_s.strip.empty? ? mac_id : name
      device_doc = {
        rt: 'device',
        rid: id,
        rn: rn_value,
        organisation_id: organisation_id,  
        device_mac: mac_id,
        ln_id: location_network_id  
      }
      client.index(index: index_name, id: "device_#{id}", body: device_doc)
    end
    
    client.indices.refresh(index: index_name)
  rescue => e
    Rails.logger.error "Associated resources index error for #{self.class.name} #{id}: #{e.message}"
  end

  def remove_from_associated_resources_index
    client = Elasticsearch::Model.client
    index_name = "associated_resource_list_#{Rails.env}"
    
    if self.is_a?(LocationNetwork)
      client.delete(index: index_name, id: "network_#{id}", ignore: 404)
    elsif self.is_a?(RouterInventory)
      client.delete(index: index_name, id: "device_#{id}", ignore: 404)
    end
    
    client.indices.refresh(index: index_name)
  rescue => e
    Rails.logger.error "Failed to remove #{self.class.name} #{id} from index: #{e.message}"
  end
end

# tagging callbacks
ActsAsTaggableOn::Tagging.class_eval do
  after_commit :index_tag_in_associated_resources, on: [:create]
  # Note: No destroy callback - tags are never removed from index
  
  def index_tag_in_associated_resources
    return unless ['RouterInventory', 'LocationNetwork'].include?(taggable_type)
    return unless context == 'tags'
    
    begin
      tag = ActsAsTaggableOn::Tag.find_by(id: tag_id)
      return unless tag
      
      client = Elasticsearch::Model.client
      index_name = "associated_resource_list_#{Rails.env}"
      
      case taggable_type
      when 'RouterInventory'
        device = RouterInventory.find_by(id: taggable_id)
        if device
          tag_doc = {
            rt: 'tag',
            rid: tag.id,
            rn: tag.name,
            organisation_id: device.organisation_id
          }
          client.index(index: index_name, id: "device_tag_#{tag.id}", body: tag_doc)
        end
        
      when 'LocationNetwork'
        network = LocationNetwork.find_by(id: taggable_id)
        if network
          tag_doc = {
            rt: 'network_tag',
            rid: tag.id,
            rn: tag.name,
            organisation_id: network.organisation_id
          }
          client.index(index: index_name, id: "network_tag_#{tag.id}", body: tag_doc)
        end
      end
      
      client.indices.refresh(index: index_name)
      Rails.logger.info "Indexed tag #{tag.name} for #{taggable_type} #{taggable_id}"
    rescue => e
      Rails.logger.error "Failed to index tag #{tag_id}: #{e.message}"
    end
  end
end