# frozen_string_literal: true

# PathRuleMatcher - Service to match Events against path_pattern Rules
#
# This service provides path pattern matching logic for evaluating whether
# an event matches a path_pattern rule. Used for hub-side testing and validation
# before agent deployment.
#
# Match Types:
#   - exact: All segments must match exactly
#   - prefix: Event path must start with rule segments
#   - suffix: Event path must end with rule segments
#   - contains: Rule segments must appear consecutively somewhere in event path
class PathRuleMatcher
  def self.matches?(rule, event)
    return false unless rule.path_pattern_rule?
    return false if event.request_segment_ids.blank?

    rule_segments = rule.path_segment_ids
    event_segments = event.request_segment_ids

    return false if rule_segments.blank?

    case rule.path_match_type
    when 'exact'
      exact_match?(event_segments, rule_segments)
    when 'prefix'
      prefix_match?(event_segments, rule_segments)
    when 'suffix'
      suffix_match?(event_segments, rule_segments)
    when 'contains'
      contains_match?(event_segments, rule_segments)
    else
      false
    end
  end

  # Find all path_pattern rules that match the given event
  def self.matching_rules(event)
    return [] if event.request_segment_ids.blank?

    Rule.path_pattern_rules.active.select do |rule|
      matches?(rule, event)
    end
  end

  # Evaluate an event against path rules and return the first matching action
  def self.evaluate(event)
    matching_rule = matching_rules(event).first
    matching_rule&.waf_action || 'allow'
  end

  private

  # Exact match: all segments must match exactly
  # Example: [1, 2, 3] matches [1, 2, 3] only
  def self.exact_match?(event_segments, rule_segments)
    event_segments == rule_segments
  end

  # Prefix match: event path must start with rule segments
  # Example: rule [1, 2] matches events [1, 2], [1, 2, 3], [1, 2, 3, 4]
  def self.prefix_match?(event_segments, rule_segments)
    return false if event_segments.length < rule_segments.length
    event_segments[0...rule_segments.length] == rule_segments
  end

  # Suffix match: event path must end with rule segments
  # Example: rule [2, 3] matches events [2, 3], [1, 2, 3], [0, 1, 2, 3]
  def self.suffix_match?(event_segments, rule_segments)
    return false if event_segments.length < rule_segments.length
    event_segments[-rule_segments.length..-1] == rule_segments
  end

  # Contains match: rule segments must appear consecutively somewhere in event path
  # Example: rule [2, 3] matches [1, 2, 3, 4], [2, 3], [0, 2, 3, 5]
  def self.contains_match?(event_segments, rule_segments)
    return false if event_segments.length < rule_segments.length

    # Check if rule_segments appear consecutively anywhere in event_segments
    (0..event_segments.length - rule_segments.length).any? do |i|
      event_segments[i, rule_segments.length] == rule_segments
    end
  end
end
