# frozen_string_literal: true

# IpRangeResolver - Service for resolving IP addresses to network ranges
#
# Provides methods to find matching network ranges for IP addresses,
# create surgical blocks, and analyze IP intelligence.
class IpRangeResolver
  # Find all network ranges that contain the given IP address
  # Returns array of hashes with range data, ordered by specificity (most specific first)
  def self.resolve(ip_address)
    return [] unless ip_address.present?

    NetworkRange.contains_ip(ip_address).map do |range|
      {
        range: range,
        cidr: range.cidr,
        prefix_length: range.prefix_length,
        specificity: range.prefix_length,
        intelligence: range.inherited_intelligence
      }
    end.sort_by { |r| -r[:specificity] } # Most specific first
  end

  # Find the most specific network range for an IP
  def self.most_specific_range(ip_address)
    resolve(ip_address).first
  end

  # Find all network ranges that overlap with a given CIDR
  def self.overlapping_ranges(cidr)
    return [] unless cidr.present?

    NetworkRange.overlapping(cidr).map do |range|
      {
        range: range,
        cidr: range.cidr,
        prefix_length: range.prefix_length,
        specificity: range.prefix_length,
        intelligence: range.inherited_intelligence
      }
    end.sort_by { |r| -r[:specificity] }
  end

  # Create network range if it doesn't exist
  def self.find_or_create_range(cidr, user: nil, source: nil, reason: nil, **attributes)
    return nil unless cidr.present?

    NetworkRange.find_or_create_by_cidr(cidr, user: user, source: source, reason: reason) do |range|
      # Try to inherit attributes from parent ranges
      inherited_attrs = inherited_attributes(cidr)
      range.assign_attributes(inherited_attrs.merge(attributes))
    end
  end

  # Create surgical block (block parent range, allow specific IP)
  def self.create_surgical_block(ip_address, parent_cidr, user: nil, reason: nil, **options)
    return [nil, nil] unless ip_address.present? && parent_cidr.present?

    Rule.create_surgical_block(ip_address, parent_cidr, user: user, reason: reason, **options)
  end

  # Get IP intelligence data
  def self.get_ip_intelligence(ip_address)
    ranges = resolve(ip_address)

    {
      ip_address: ip_address,
      ranges: ranges,
      most_specific_range: ranges.first,
      intelligence: ranges.first&.dig(:intelligence) || {},

      # Suggested blocking ranges
      suggested_blocks: suggest_blocking_ranges(ip_address, ranges)
    }
  end

  # Suggest CIDR ranges for blocking based on network hierarchy
  def self.suggest_blocking_ranges(ip_address, ranges = nil)
    ranges ||= resolve(ip_address)
    return [] if ranges.empty?

    ip_obj = IPAddr.new(ip_address)
    suggestions = []

    # Current /32 or /128 (single IP)
    suggestions << {
      cidr: "#{ip_address}/#{ip_obj.ipv4? ? '32' : '128'}",
      type: 'single_ip',
      description: 'Single IP address',
      current_block: ranges.any? { |r| r[:prefix_length] == (ip_obj.ipv4? ? 32 : 128) }
    }

    # Look for common network sizes
    if ip_obj.ipv4?
      [24, 23, 22, 21, 20, 19, 18, 16].each do |prefix|
        network_cidr = calculate_network_cidr(ip_address, prefix)
        next unless network_cidr

        suggestions << {
          cidr: network_cidr,
          type: 'network_block',
          description: "/#{prefix} network block",
          current_block: ranges.any? { |r| r[:prefix_length] == prefix },
          existing_range: ranges.find { |r| r[:prefix_length] <= prefix }
        }
      end
    end

    suggestions
  end

  # Find related IPs from same network ranges
  def self.find_related_ips(ip_address, limit_per_range: 100, total_limit: 500)
    ranges = resolve(ip_address)
    return [] if ranges.empty?

    related_ips = {}

    ranges.each do |range_data|
      range = range_data[:range]

      # Find events from this range (excluding the original IP)
      events = Event.where("ip_address <<= ?", range.cidr)  # Postgres <<= operator
                   .where.not(ip_address: ip_address)
                   .limit(limit_per_range)
                   .distinct(:ip_address)
                   .pluck(:ip_address)

      related_ips[range.cidr] = events unless events.empty?

      break if related_ips.values.flatten.size >= total_limit
    end

    related_ips
  end

  # Check if IP is currently blocked by any rule
  def self.ip_blocked?(ip_address)
    ranges = resolve(ip_address)
    return false if ranges.empty?

    range_ids = ranges.map { |r| r[:range].id }

    Rule.network_rules
        .where(network_range_id: range_ids)
        .where(action: 'deny')
        .enabled
        .where("expires_at IS NULL OR expires_at > ?", Time.current)
        .exists?
  end

  # Get blocking rules for an IP
  def self.blocking_rules_for_ip(ip_address)
    ranges = resolve(ip_address)
    return Rule.none if ranges.empty?

    range_ids = ranges.map { |r| r[:range].id }

    Rule.network_rules
        .where(network_range_id: range_ids)
        .where(action: 'deny')
        .enabled
        .where("expires_at IS NULL OR expires_at > ?", Time.current)
        .includes(:network_range)
        .order('network_ranges.network_prefix DESC')
  end

  # Analyze traffic patterns for a network range
  def self.analyze_network_traffic(cidr, time_range: 1.week.ago..Time.current)
    network_range = NetworkRange.find_by(network: cidr)
    return nil unless network_range

    events = Event.where("ip_address <<= ?", cidr)  # Postgres <<= operator
                 .where(timestamp: time_range)

    {
      network_range: network_range,
      total_requests: events.count,
      unique_ips: events.distinct.count(:ip_address),
      blocked_requests: events.blocked.count,
      allowed_requests: events.allowed.count,
      top_paths: events.group(:request_path).count.sort_by { |_, count| -count }.first(10),
      top_user_agents: events.group(:user_agent).count.sort_by { |_, count| -count }.first(5),
      time_distribution: events.group_by_hour(:timestamp).count
    }
  end

  private

  # Inherit attributes from parent network ranges
  def self.inherited_attributes(cidr)
    ip_obj = IPAddr.new(cidr)

    parent = NetworkRange.where("network <<= ? AND masklen(network) < ?", cidr, ip_obj.prefixlen)
                        .where.not(asn: nil)
                        .order("masklen(network) DESC")
                        .first

    if parent
      {
        asn: parent.asn,
        asn_org: parent.asn_org,
        company: parent.company,
        country: parent.country,
        is_datacenter: parent.is_datacenter,
        is_proxy: parent.is_proxy,
        is_vpn: parent.is_vpn
      }
    else
      {}
    end
  end

  # Calculate network CIDR for an IP and prefix length
  def self.calculate_network_cidr(ip_address, prefix_length)
    ip_obj = IPAddr.new(ip_address)
    network = ip_obj.mask(prefix_length)
    "#{network}/#{prefix_length}"
  rescue IPAddr::InvalidAddressError
    nil
  end
end