# frozen_string_literal: true

# WafPolicyMatcher - Service to match Events against active WafPolicies
#
# This service provides efficient matching of events against firewall policies
# (both network-based and path-based) and can generate rules when matches are found.
class WafPolicyMatcher
  include ActiveModel::Model
  include ActiveModel::Attributes

  attr_accessor :event
  attr_reader :matching_policies, :generated_rules

  def initialize(event:)
    @event = event
    @matching_policies = []
    @generated_rules = []
  end

  # Helper method to get network range from event
  def network_range
    event&.network_range
  end

  # Find all active policies that match the given event (network or path-based)
  def find_matching_policies
    return [] unless event.present?

    @matching_policies = active_policies.select do |policy|
      policy.matches_event?(event)
    end

    # Sort by priority: path_pattern > country > asn > company > network_type, then by creation date
    @matching_policies.sort_by do |policy|
      priority_score = case policy.policy_type
                      when 'path_pattern'
                        1  # Highest priority for path-specific rules
                      when 'country'
                        2
                      when 'asn'
                        3
                      when 'company'
                        4
                      when 'network_type'
                        5
                      else
                        99
                      end

      [priority_score, policy.created_at]
    end
  end

  # Generate rules from matching policies
  def generate_rules
    return [] if matching_policies.empty?

    @generated_rules = matching_policies.map do |policy|
      # Use the policy's event-based rule creation method
      rule = policy.create_rule_for_event(event)

      if rule
        if rule.persisted?
          Rails.logger.info "Generated rule for event #{event.id} from policy #{policy.name}"
          rule
        else
          # Rule creation failed validation
          Rails.logger.warn "Failed to create rule for event #{event.id}: #{rule.errors.full_messages.join(', ')}"
          nil
        end
      else
        # Policy didn't match or returned nil (e.g., supernet already exists)
        nil
      end
    end.compact
  end

  # Find and generate rules in one step
  def match_and_generate_rules
    find_matching_policies
    generate_rules

    # Return hash format expected by ProcessWafPoliciesJob
    {
      matching_policies: @matching_policies,
      generated_rules: @generated_rules
    }
  end

  # Class methods for batch processing
  def self.process_event(event)
    matcher = new(event: event)
    matcher.match_and_generate_rules
  end

  # Legacy method for backward compatibility - converts network range to event
  def self.process_network_range(network_range)
    # Find the most recent event for this network range
    sample_event = network_range.events.order(created_at: :desc).first
    if sample_event
      process_event(sample_event)
    else
      # No events exist for this network range, return empty results
      # Network-based policies need real events to trigger rule creation
      { matching_policies: [], generated_rules: [] }
    end
  end

  # Evaluate an event against policies and mark its network range as evaluated
  # This is the main entry point for inline policy evaluation
  def self.evaluate_and_mark!(event)
    return { matching_policies: [], generated_rules: [] } unless event

    matcher = new(event: event)
    result = matcher.match_and_generate_rules

    # Mark the event's network range as evaluated
    if event.network_range
      event.network_range.update_column(:policies_evaluated_at, Time.current)
    end

    result
  end

  # Legacy method for backward compatibility
  def self.evaluate_and_mark_network_range!(network_range)
    return { matching_policies: [], generated_rules: [] } unless network_range

    # Find the most recent event for this network range
    sample_event = network_range.events.order(created_at: :desc).first
    if sample_event
      evaluate_and_mark!(sample_event)
    else
      # No events exist, use the old network-range based evaluation
      process_network_range(network_range)
      network_range.update_column(:policies_evaluated_at, Time.current)
      { matching_policies: [], generated_rules: [] }
    end
  end

  def self.batch_process_network_ranges(network_ranges)
    results = []

    network_ranges.each do |network_range|
      matcher = new(network_range: network_range)
      result = matcher.match_and_generate_rules
      results << {
        network_range: network_range,
        matching_policies: matcher.matching_policies,
        generated_rules: matcher.generated_rules
      }
    end

    results
  end

  # Process network ranges that need policy evaluation
  def self.process_ranges_without_policy_rules(limit: 100)
    # Find network ranges that don't have policy-generated rules
    # but have intelligence data that could match policies
    ranges_needing_evaluation = NetworkRange
      .left_joins(:rules)
      .where("rules.id IS NULL OR rules.waf_policy_id IS NULL")
      .where("(country IS NOT NULL OR asn IS NOT NULL OR company IS NOT NULL OR is_datacenter = true OR is_proxy = true OR is_vpn = true)")
      .limit(limit)
      .includes(:rules)

    batch_process_network_ranges(ranges_needing_evaluation)
  end

  # Re-evaluate all network ranges for policy changes
  def self.reprocess_all_for_policy(waf_policy)
    # Find all network ranges that could potentially match this policy
    potential_ranges = case waf_policy.policy_type
                      when 'country'
                        NetworkRange.where(country: waf_policy.targets)
                      when 'asn'
                        NetworkRange.where(asn: waf_policy.targets)
                      when 'network_type'
                        NetworkRange.where(
                          "is_datacenter = ? OR is_proxy = ? OR is_vpn = ?",
                          waf_policy.targets.include?('datacenter'),
                          waf_policy.targets.include?('proxy'),
                          waf_policy.targets.include?('vpn')
                        )
                      when 'company'
                        # For company matching, we need to do text matching
                        NetworkRange.where("company ILIKE ANY (array[?])",
                          waf_policy.targets.map { |c| "%#{c}%" })
                      else
                        NetworkRange.none
                      end

    results = []
    potential_ranges.find_each do |network_range|
      matcher = new(network_range: network_range)
      if waf_policy.matches_network_range?(network_range)
        # Check for supernet rules before creating
        if network_range.supernet_rules.any?
          supernet = network_range.supernet_rules.first
          Rails.logger.info "Skipping rule for #{network_range.cidr} - covered by supernet rule ##{supernet.id}"
          next
        end

        rule = waf_policy.create_rule_for_network_range(network_range)
        if rule&.persisted?
          results << { network_range: network_range, generated_rule: rule }
        elsif rule
          Rails.logger.warn "Failed to create rule for #{network_range.cidr}: #{rule.errors.full_messages.join(', ')}"
        end
      end
    end

    results
  end

  # Statistics and reporting
  def self.matching_policies_for_network_range(network_range)
    matcher = new(network_range: network_range)
    matcher.find_matching_policies
  end

  def self.policy_effectiveness_stats(waf_policy, days: 30)
    cutoff_date = days.days.ago

    rules = waf_policy.generated_rules.where('created_at > ?', cutoff_date)

    {
      policy_name: waf_policy.name,
      policy_type: waf_policy.policy_type,
      action: waf_policy.policy_action,
      rules_generated: rules.count,
      active_rules: rules.active.count,
      networks_protected: rules.joins(:network_range).count('distinct network_ranges.id'),
      period_days: days,
      generation_rate: rules.count.to_f / days
    }
  end

  private

  def active_policies
    @active_policies ||= WafPolicy.active
  end
end