# frozen_string_literal: true

require "test_helper"

class PathScannerDetectorJobTest < ActiveJob::TestCase
  setup do
    @project = Project.first || #Project.create!(
      name: "Test Project",
      slug: "test-project",
      public_key: SecureRandom.hex(16)
    )
  end

  test "creates ban rule for IP hitting scanner paths" do
    ip = "192.168.1.100"

    # Create events hitting scanner paths
    ["/.env", "/.git", "/wp-admin"].each do |path|
      Event.create!(
        project: @project,
        request_id: SecureRandom.uuid,
        timestamp: Time.current,
        ip_address: ip,
        request_path: path,
        waf_action: "allow"
      )
    end

    count = PathScannerDetectorJob.perform_now

    assert_equal 1, count

    rule = Rule.where(source: "auto:scanner_detected").last
    assert_not_nil rule
    assert_equal "network", rule.waf_rule_type
    assert_equal "deny", rule.waf_action
    assert_equal "#{ip}/32", rule.cidr
    assert_equal 32, rule.priority
    assert rule.enabled?
  end

  test "sets 24 hour expiration on ban rules" do
    ip = "192.168.1.100"

    3.times do |i|
      Event.create!(
        project: @project,
        request_id: SecureRandom.uuid,
        timestamp: Time.current,
        ip_address: ip,
        request_path: "/.env",
        waf_action: "allow"
      )
    end

    PathScannerDetectorJob.perform_now

    rule = Rule.where(source: "auto:scanner_detected").last
    assert_not_nil rule.expires_at

    # Should expire in approximately 24 hours
    time_until_expiry = rule.expires_at - Time.current
    assert time_until_expiry > 23.hours
    assert time_until_expiry < 25.hours
  end

  test "includes metadata about detected paths" do
    ip = "192.168.1.100"

    paths = ["/.env", "/.git", "/wp-admin"]
    paths.each do |path|
      Event.create!(
        project: @project,
        request_id: SecureRandom.uuid,
        timestamp: Time.current,
        ip_address: ip,
        request_path: path,
        waf_action: "allow"
      )
    end

    PathScannerDetectorJob.perform_now

    rule = Rule.where(source: "auto:scanner_detected").last
    assert_equal 3, rule.metadata["hit_count"]
    assert_equal paths.sort, rule.metadata["paths"].sort
    assert rule.metadata["reason"].include?("Scanner detected")
    assert rule.metadata["auto_generated"]
  end

  test "does not create rule for insufficient hits" do
    ip = "192.168.1.100"

    # Only 2 hits, minimum is 3
    2.times do
      Event.create!(
        project: @project,
        request_id: SecureRandom.uuid,
        timestamp: Time.current,
        ip_address: ip,
        request_path: "/.env",
        waf_action: "allow"
      )
    end

    count = PathScannerDetectorJob.perform_now

    assert_equal 0, count
  end

  test "only considers recent events" do
    ip = "192.168.1.100"

    # Old event (outside lookback window)
    old_event = Event.create!(
      project: @project,
      request_id: SecureRandom.uuid,
      timestamp: 10.minutes.ago,
      ip_address: ip,
      request_path: "/.env",
      waf_action: "allow"
    )

    # Recent events
    2.times do
      Event.create!(
        project: @project,
        request_id: SecureRandom.uuid,
        timestamp: Time.current,
        ip_address: ip,
        request_path: "/.git",
        waf_action: "allow"
      )
    end

    count = PathScannerDetectorJob.perform_now

    # Should not find sufficient hits (only 2 recent, 1 old)
    assert_equal 0, count
  end

  test "does not create duplicate rules for existing IP" do
    ip = "192.168.1.100"

    # Create existing rule
    Rule.create!(
      rule_type: "network_v4",
      action: "deny",
      conditions: { cidr: "#{ip}/32" },
      enabled: true
    )

    # Create scanner events
    3.times do
      Event.create!(
        project: @project,
        request_id: SecureRandom.uuid,
        timestamp: Time.current,
        ip_address: ip,
        request_path: "/.env",
        waf_action: "allow"
      )
    end

    count = PathScannerDetectorJob.perform_now

    assert_equal 0, count
  end

  test "handles IPv6 addresses" do
    ip = "2001:db8::1"

    3.times do
      Event.create!(
        project: @project,
        request_id: SecureRandom.uuid,
        timestamp: Time.current,
        ip_address: ip,
        request_path: "/.env",
        waf_action: "allow"
      )
    end

    count = PathScannerDetectorJob.perform_now

    assert_equal 1, count

    rule = Rule.where(source: "auto:scanner_detected").last
    assert_equal "network", rule.waf_rule_type
    assert_equal "#{ip}/32", rule.cidr
  end

  test "creates separate rules for different IPs" do
    ip1 = "192.168.1.100"
    ip2 = "192.168.1.101"

    [ip1, ip2].each do |ip|
      3.times do
        Event.create!(
          project: @project,
          request_id: SecureRandom.uuid,
          timestamp: Time.current,
          ip_address: ip,
          request_path: "/.env",
          waf_action: "allow"
        )
      end
    end

    count = PathScannerDetectorJob.perform_now

    assert_equal 2, count
  end

  test "handles invalid IP addresses gracefully" do
    # Create event with invalid IP
    Event.create!(
      project: @project,
      request_id: SecureRandom.uuid,
      timestamp: Time.current,
      ip_address: "invalid-ip",
      request_path: "/.env",
      waf_action: "allow"
    )

    assert_nothing_raised do
      PathScannerDetectorJob.perform_now
    end
  end

  test "returns count of created rules" do
    3.times do |i|
      ip = "192.168.1.#{100 + i}"

      3.times do
        Event.create!(
          project: @project,
          request_id: SecureRandom.uuid,
          timestamp: Time.current,
          ip_address: ip,
          request_path: "/.env",
          waf_action: "allow"
        )
      end
    end

    count = PathScannerDetectorJob.perform_now

    assert_equal 3, count
  end
end
