# frozen_string_literal: true

# WafPolicy - High-level firewall policies that generate specific Rules
#
# WafPolicies contain strategic decisions like "Block Brazil" that automatically
# generate specific Rules when matching network ranges are discovered.
class WafPolicy < ApplicationRecord
  # Policy types - different categories of blocking rules
  POLICY_TYPES = %w[country asn company network_type path_pattern].freeze

  # Actions - what to do when traffic matches this policy
  ACTIONS = %w[allow deny redirect challenge].freeze

  # Associations
  belongs_to :user
  has_many :generated_rules, class_name: 'Rule', dependent: :destroy

  # Validations
  validates :name, presence: true, uniqueness: true
  validates :policy_type, presence: true, inclusion: { in: POLICY_TYPES }
  validates :policy_action, presence: true, inclusion: { in: ACTIONS }
  validates :targets, presence: true
validate :targets_must_be_array
  validates :user, presence: true
  validate :validate_targets_by_type
  validate :validate_redirect_configuration, if: :redirect_policy_action?
  validate :validate_challenge_configuration, if: :challenge_policy_action?

  # Scopes
  scope :enabled, -> { where(enabled: true) }
  scope :disabled, -> { where(enabled: false) }
  scope :active, -> { enabled.where("expires_at IS NULL OR expires_at > ?", Time.current) }
  scope :expired, -> { where("expires_at IS NOT NULL AND expires_at <= ?", Time.current) }
  scope :by_type, ->(type) { where(policy_type: type) }
  scope :country, -> { by_type('country') }
  scope :asn, -> { by_type('asn') }
  scope :company, -> { by_type('company') }
  scope :network_type, -> { by_type('network_type') }

  # Callbacks
  before_validation :set_defaults

  # Policy type methods
  def country_policy?
    policy_type == 'country'
  end

  def asn_policy?
    policy_type == 'asn'
  end

  def company_policy?
    policy_type == 'company'
  end

  def network_type_policy?
    policy_type == 'network_type'
  end

  def path_pattern_policy?
    policy_type == 'path_pattern'
  end

  # Action methods
  def allow_action?
    policy_action == 'allow'
  end

  def deny_action?
    policy_action == 'deny'
  end

  def redirect_action?
    policy_action == 'redirect'
  end

  def challenge_action?
    policy_action == 'challenge'
  end

  # Policy action methods (to avoid confusion with Rails' action methods)
  def allow_policy_action?
    policy_action == 'allow'
  end

  def deny_policy_action?
    policy_action == 'deny'
  end

  def redirect_policy_action?
    policy_action == 'redirect'
  end

  def challenge_policy_action?
    policy_action == 'challenge'
  end

  # Lifecycle methods
  def active?
    enabled? && !expired?
  end

  def expired?
    expires_at.present? && expires_at <= Time.current
  end

  def activate!
    update!(enabled: true)
  end

  def deactivate!
    update!(enabled: false)
  end

  def expire!
    update!(expires_at: Time.current)
  end

  # Network range matching methods
  def matches_network_range?(network_range)
    return false unless active?

    case policy_type
    when 'country'
      matches_country?(network_range)
    when 'asn'
      matches_asn?(network_range)
    when 'company'
      matches_company?(network_range)
    when 'network_type'
      matches_network_type?(network_range)
    else
      false
    end
  end

  # Event matching methods (for path patterns)
  def matches_event?(event)
    return false unless active?

    case policy_type
    when 'country', 'asn', 'company', 'network_type'
      # For network-based policies, use the event's network range
      event.network_range && matches_network_range?(event.network_range)
    when 'path_pattern'
      matches_path_patterns?(event)
    else
      false
    end
  end

  def create_rule_for_network_range(network_range)
    return nil unless matches_network_range?(network_range)

    rule = Rule.create!(
      rule_type: 'network',
      action: policy_action,
      network_range: network_range,
      waf_policy: self,
      user: user,
      source: "policy",
      metadata: build_rule_metadata(network_range),
      priority: network_range.prefix_length
    )

    # Handle redirect/challenge specific data
    if redirect_action? && additional_data['redirect_url']
      rule.update!(
        metadata: rule.metadata.merge(
          redirect_url: additional_data['redirect_url'],
          redirect_status: additional_data['redirect_status'] || 302
        )
      )
    elsif challenge_action?
      rule.update!(
        metadata: rule.metadata.merge(
          challenge_type: additional_data['challenge_type'] || 'captcha',
          challenge_message: additional_data['challenge_message']
        )
      )
    end

    rule
  end

  def create_rule_for_event(event)
    return nil unless matches_event?(event)

    # For path pattern policies, create a path_pattern rule
    if path_pattern_policy?
      # Check for existing path_pattern rule with same policy and patterns
      existing_rule = Rule.find_by(
        waf_rule_type: 'path_pattern',
        waf_action: policy_action,
        waf_policy: self,
        enabled: true
      )

      if existing_rule
        Rails.logger.debug "Path pattern rule already exists for policy #{name}"
        return existing_rule
      end

      rule = Rule.create!(
        waf_rule_type: 'path_pattern',
        waf_action: policy_action,
        waf_policy: self,
        user: user,
        source: "policy",
        conditions: build_path_pattern_conditions(event),
        metadata: build_path_pattern_metadata(event),
        priority: 50  # Default priority for path rules
      )

      # Handle redirect/challenge specific data
      if redirect_action? && additional_data['redirect_url']
        rule.update!(
          metadata: rule.metadata.merge(
            redirect_url: additional_data['redirect_url'],
            redirect_status: additional_data['redirect_status'] || 302
          )
        )
      elsif challenge_action?
        rule.update!(
          metadata: rule.metadata.merge(
            challenge_type: additional_data['challenge_type'] || 'captcha',
            challenge_message: additional_data['challenge_message']
          )
        )
      end

      rule
    else
      # For network-based policies, fall back to network range rule creation
      create_rule_for_network_range(event.network_range)
    end
  end

  # Class methods for creating common policies
  def self.create_country_policy(countries, policy_action: 'deny', user:, **options)
    create!(
      name: "#{policy_action.capitalize} #{countries.join(', ')}",
      policy_type: 'country',
      targets: Array(countries),
      policy_action: policy_action,
      user: user,
      **options
    )
  end

  def self.create_asn_policy(asns, policy_action: 'deny', user:, **options)
    create!(
      name: "#{policy_action.capitalize} ASNs #{asns.join(', ')}",
      policy_type: 'asn',
      targets: Array(asns).map(&:to_i),
      policy_action: policy_action,
      user: user,
      **options
    )
  end

  def self.create_company_policy(companies, policy_action: 'deny', user:, **options)
    create!(
      name: "#{policy_action.capitalize} #{companies.join(', ')}",
      policy_type: 'company',
      targets: Array(companies),
      policy_action: policy_action,
      user: user,
      **options
    )
  end

  def self.create_network_type_policy(types, policy_action: 'deny', user:, **options)
    create!(
      name: "#{policy_action.capitalize} #{types.join(', ')}",
      policy_type: 'network_type',
      targets: Array(types),
      policy_action: policy_action,
      user: user,
      **options
    )
  end

  def self.create_path_pattern_policy(patterns, policy_action: 'deny', user:, **options)
    create!(
      name: "#{policy_action.capitalize} path patterns: #{Array(patterns).join(', ')}",
      policy_type: 'path_pattern',
      targets: Array(patterns),
      policy_action: policy_action,
      user: user,
      **options
    )
  end

  # Redirect/challenge specific methods
  def redirect_url
    additional_data&.dig('redirect_url')
  end

  def redirect_status
    additional_data&.dig('redirect_status') || 302
  end

  def challenge_type
    additional_data&.dig('challenge_type') || 'captcha'
  end

  def challenge_message
    additional_data&.dig('challenge_message')
  end

  # Statistics and analytics
  def generated_rules_count
    generated_rules.count
  end

  def active_rules_count
    generated_rules.active.count
  end

  def effectiveness_stats
    recent_rules = generated_rules.where('created_at > ?', 7.days.ago)

    {
      total_rules_generated: generated_rules_count,
      active_rules: active_rules_count,
      rules_last_7_days: recent_rules.count,
      policy_type: policy_type,
      policy_action: policy_action,
      targets_count: targets&.length || 0
    }
  end

  # String representations
  def to_s
    name
  end

  def to_param
    name.parameterize
  end

  private

  def set_defaults
    self.targets ||= []
    self.additional_data ||= {}
    self.enabled = true if enabled.nil?
  end

  def targets_must_be_array
    unless targets.is_a?(Array)
      errors.add(:targets, "must be an array")
    end
  end

  def validate_targets_by_type
    return if targets.blank?

    case policy_type
    when 'country'
      validate_country_targets
    when 'asn'
      validate_asn_targets
    when 'company'
      validate_company_targets
    when 'network_type'
      validate_network_type_targets
    when 'path_pattern'
      validate_path_pattern_targets
    end
  end

  def validate_country_targets
    unless targets.all? { |target| target.is_a?(String) && target.match?(/\A[A-Z]{2}\z/) }
      errors.add(:targets, "must be valid ISO country codes (e.g., 'BR', 'US')")
    end
  end

  def validate_asn_targets
    unless targets.all? { |target| target.is_a?(Integer) && target > 0 }
      errors.add(:targets, "must be valid ASNs (positive integers)")
    end
  end

  def validate_company_targets
    unless targets.all? { |target| target.is_a?(String) && target.present? }
      errors.add(:targets, "must be valid company names")
    end
  end

  def validate_network_type_targets
    valid_types = %w[datacenter proxy vpn standard]
    unless targets.all? { |target| valid_types.include?(target) }
      errors.add(:targets, "must be one of: #{valid_types.join(', ')}")
    end
  end

  def validate_path_pattern_targets
    unless targets.all? { |target| target.is_a?(String) && target.present? }
      errors.add(:targets, "must be valid path pattern strings")
    end

    # Validate path patterns format (basic validation)
    targets.each do |pattern|
      begin
        # Basic validation - ensure it's a reasonable pattern
        unless pattern.match?(/\A[a-zA-Z0-9\-\._\*\/\?\[\]\{\}]+\z/)
          errors.add(:targets, "contains invalid characters in pattern: #{pattern}")
        end
      rescue => e
        errors.add(:targets, "invalid path pattern: #{pattern} - #{e.message}")
      end
    end
  end

  def validate_redirect_configuration
    if additional_data['redirect_url'].blank?
      errors.add(:additional_data, "must include 'redirect_url' for redirect action")
    end
  end

  def validate_challenge_configuration
    # Challenge is flexible - can use defaults if not specified
    valid_challenge_types = %w[captcha javascript proof_of_work]
    challenge_type_value = additional_data&.dig('challenge_type')

    if challenge_type_value && !valid_challenge_types.include?(challenge_type_value)
      errors.add(:additional_data, "challenge_type must be one of: #{valid_challenge_types.join(', ')}")
    end
  end

  # Matching logic for different policy types
  def matches_country?(network_range)
    country = network_range.country || network_range.inherited_intelligence[:country]
    targets.include?(country)
  end

  def matches_asn?(network_range)
    asn = network_range.asn || network_range.inherited_intelligence[:asn]
    targets.include?(asn)
  end

  def matches_company?(network_range)
    company = network_range.company || network_range.inherited_intelligence[:company]
    return false if company.blank?

    targets.any? do |target_company|
      company.downcase.include?(target_company.downcase) ||
      target_company.downcase.include?(company.downcase)
    end
  end

  def matches_network_type?(network_range)
    intelligence = network_range.inherited_intelligence

    targets.any? do |target_type|
      case target_type
      when 'datacenter'
        intelligence[:is_datacenter] == true
      when 'proxy'
        intelligence[:is_proxy] == true
      when 'vpn'
        intelligence[:is_vpn] == true
      when 'standard'
        intelligence[:is_datacenter] == false &&
        intelligence[:is_proxy] == false &&
        intelligence[:is_vpn] == false
      else
        false
      end
    end
  end

  def build_rule_metadata(network_range)
    base_metadata = {
      generated_by_policy: id,
      policy_name: name,
      policy_type: policy_type,
      matched_field: matched_field(network_range),
      matched_value: matched_value(network_range)
    }

    base_metadata.merge!(additional_data || {})
  end

  def matched_field(network_range)
    case policy_type
    when 'country'
      'country'
    when 'asn'
      'asn'
    when 'company'
      'company'
    when 'network_type'
      'network_type'
    else
      'unknown'
    end
  end

  def matched_value(network_range)
    case policy_type
    when 'country'
      network_range.country || network_range.inherited_intelligence[:country]
    when 'asn'
      network_range.asn || network_range.inherited_intelligence[:asn]
    when 'company'
      network_range.company || network_range.inherited_intelligence[:company]
    when 'network_type'
      intelligence = network_range.inherited_intelligence
      types = []
      types << 'datacenter' if intelligence[:is_datacenter]
      types << 'proxy' if intelligence[:is_proxy]
      types << 'vpn' if intelligence[:is_vpn]
      types.join(',') || 'standard'
    end
  end

  # Path pattern matching methods
  def matches_path_patterns?(event)
    return false if event.request_path.blank?

    path = event.request_path.downcase
    targets.any? { |pattern| matches_path_pattern?(pattern, path) }
  end

  def matches_path_pattern?(pattern, path)
    pattern = pattern.downcase

    # Handle different pattern types
    case pattern
    when /\*/, /\?/, /\[/
      # Glob patterns - simple matching
      match_glob_pattern(pattern, path)
    when /\.php$/, /\.exe$/, /\.js$/
      # File extension patterns
      path.end_with?(pattern)
    when /\A\//
      # Exact path match
      path == pattern
    else
      # Simple substring match
      path.include?(pattern)
    end
  end

  def match_glob_pattern(pattern, path)
    # Convert simple glob patterns to regex
    regex_pattern = pattern
      .gsub('*', '.*')
      .gsub('?', '.')
      .gsub('[', '\[')
      .gsub(']', '\]')

    path.match?(/\A#{regex_pattern}\z/)
  end

  def build_path_pattern_conditions(event)
    {
      "patterns" => targets,
      "match_type" => "path_pattern"
    }
  end

  def build_path_pattern_metadata(event)
    base_metadata = {
      generated_by_policy: id,
      policy_name: name,
      policy_type: policy_type,
      matched_path: event.request_path,
      generated_from: "event"
    }

    base_metadata.merge!(additional_data || {})
  end
end
