require "test_helper"

class WafPolicyMatcherTest < ActiveSupport::TestCase
  setup do
    @user = users(:jason)
    @network_range = NetworkRange.create!(
      network: "192.168.1.0/24",
      country: "BR",
      company: "Test Company",
      asn: 12345,
      is_datacenter: false
    )
    @matcher = WafPolicyMatcher.new(network_range: @network_range)
  end

  teardown do
    WafPolicy.delete_all
    Rule.delete_all
    NetworkRange.delete_all
  end

  # Initialization
  test "initializes with network range" do
    assert_equal @network_range, @matcher.network_range
    assert_equal [], @matcher.matching_policies
    assert_equal [], @matcher.generated_rules
  end

  test "handles nil network range" do
    matcher = WafPolicyMatcher.new(network_range: nil)
    assert_nil matcher.network_range
  end

  # Policy Matching
  test "find_matching_policies returns policies that match network range" do
    # Create policies that should match
    brazil_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")
    test_asn_policy = WafPolicy.create_asn_policy([12345], 'challenge', @user, "Challenge ASN")
    test_company_policy = WafPolicy.create_company_policy(['Test Company'], 'redirect', @user, "Redirect Company")

    # Create policies that should not match
    us_policy = WafPolicy.create_country_policy(['US'], 'deny', @user, "Block US")
    other_asn_policy = WafPolicy.create_asn_policy([67890], 'deny', @user, "Block Other ASN")

    matching_policies = @matcher.find_matching_policies

    assert_includes matching_policies, brazil_policy
    assert_includes matching_policies, test_asn_policy
    assert_includes matching_policies, test_company_policy
    assert_not_includes matching_policies, us_policy
    assert_not_includes matching_policies, other_asn_policy
  end

  test "find_matching_policies sorts by policy type priority" do
    # Create different policy types with same creation time
    base_time = 1.hour.ago

    # Country policy (highest priority)
    country_policy = WafPolicy.create!(
      name: "Country Policy",
      policy_type: "country",
      targets: ["BR"],
      policy_action: "deny",
      user: @user,
      created_at: base_time
    )

    # ASN policy (second priority)
    asn_policy = WafPolicy.create!(
      name: "ASN Policy",
      policy_type: "asn",
      targets: [12345],
      policy_action: "deny",
      user: @user,
      created_at: base_time
    )

    # Company policy (third priority)
    company_policy = WafPolicy.create!(
      name: "Company Policy",
      policy_type: "company",
      targets: ["Test Company"],
      policy_action: "deny",
      user: @user,
      created_at: base_time
    )

    # Network type policy (lowest priority)
    network_type_policy = WafPolicy.create!(
      name: "Network Type Policy",
      policy_type: "network_type",
      targets: ["standard"],
      policy_action: "deny",
      user: @user,
      created_at: base_time
    )

    matching_policies = @matcher.find_matching_policies

    # Should be ordered by priority: country > asn > company > network_type
    assert_equal country_policy, matching_policies[0]
    assert_equal asn_policy, matching_policies[1]
    assert_equal company_policy, matching_policies[2]
    assert_equal network_type_policy, matching_policies[3]
  end

  test "find_matching_policies sorts by creation date for same priority" do
    # Create two country policies with different creation times
    older_policy = WafPolicy.create!(
      name: "Older Policy",
      policy_type: "country",
      targets: ["BR"],
      policy_action: "deny",
      user: @user,
      created_at: 2.hours.ago
    )

    newer_policy = WafPolicy.create!(
      name: "Newer Policy",
      policy_type: "country",
      targets: ["BR"],
      policy_action: "deny",
      user: @user,
      created_at: 1.hour.ago
    )

    matching_policies = @matcher.find_matching_policies

    # Newer policy should come first
    assert_equal newer_policy, matching_policies[0]
    assert_equal older_policy, matching_policies[1]
  end

  test "find_matching_policies skips inactive policies" do
    # Create active policy
    active_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Active Policy")

    # Create disabled policy
    disabled_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Disabled Policy")
    disabled_policy.update!(enabled: false)

    # Create expired policy
    expired_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Expired Policy")
    expired_policy.update!(expires_at: 1.hour.ago)

    matching_policies = @matcher.find_matching_policies

    assert_includes matching_policies, active_policy
    assert_not_includes matching_policies, disabled_policy
    assert_not_includes matching_policies, expired_policy
  end

  test "find_matching_policies returns empty array for nil network range" do
    matcher = WafPolicyMatcher.new(network_range: nil)
    matching_policies = matcher.find_matching_policies
    assert_equal [], matching_policies
  end

  # Rule Generation
  test "generate_rules creates rules for matching policies" do
    brazil_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")
    @matcher.instance_variable_set(:@matching_policies, [brazil_policy])

    generated_rules = @matcher.generate_rules

    assert_equal 1, generated_rules.length
    rule = generated_rules.first
    assert_equal brazil_policy, rule.waf_policy
    assert_equal @network_range, rule.network_range
    assert_equal "deny", rule.waf_action
  end

  test "generate_rules handles multiple matching policies" do
    policies = [
      WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil"),
      WafPolicy.create_asn_policy([12345], 'challenge', @user, "Challenge ASN"),
      WafPolicy.create_company_policy(['Test Company'], 'redirect', @user, "Redirect Company")
    ]
    @matcher.instance_variable_set(:@matching_policies, policies)

    generated_rules = @matcher.generate_rules

    assert_equal 3, generated_rules.length
    assert_equal "deny", generated_rules[0].action
    assert_equal "challenge", generated_rules[1].action
    assert_equal "redirect", generated_rules[2].action
  end

  test "generate_rules returns existing rules instead of duplicates" do
    policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")

    # Create existing rule
    existing_rule = Rule.create!(
      rule_type: "network",
      action: "deny",
      network_range: @network_range,
      waf_policy: policy,
      user: @user,
      enabled: true
    )

    @matcher.instance_variable_set(:@matching_policies, [policy])
    generated_rules = @matcher.generate_rules

    assert_equal 1, generated_rules.length
    assert_equal existing_rule, generated_rules.first
  end

  test "generate_rules handles policy that fails to create rule" do
    policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")

    # Mock policy to return nil for rule creation (e.g., expired policy)
    policy.expects(:create_rule_for_network_range).with(@network_range).returns(nil)

    @matcher.instance_variable_set(:@matching_policies, [policy])
    generated_rules = @matcher.generate_rules

    assert_equal [], generated_rules
  end

  test "generate_rules returns empty array for no matching policies" do
    @matcher.instance_variable_set(:@matching_policies, [])
    generated_rules = @matcher.generate_rules
    assert_equal [], generated_rules
  end

  # Combined Operations
  test "match_and_generate_rules does both operations" do
    brazil_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")

    result = @matcher.match_and_generate_rules

    assert_equal 1, result[:matching_policies].length
    assert_equal 1, result[:generated_rules].length
    assert_includes result[:matching_policies], brazil_policy
    assert_equal brazil_policy, result[:generated_rules].first.waf_policy
  end

  # Class Methods
  test "self.process_network_range creates matcher and processes" do
    policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")

    result = WafPolicyMatcher.process_network_range(@network_range)

    assert_equal 1, result[:matching_policies].length
    assert_equal 1, result[:generated_rules].length
  end

  test "self.evaluate_and_mark! processes and marks as evaluated" do
    policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")

    original_evaluated_at = @network_range.policies_evaluated_at
    result = WafPolicyMatcher.evaluate_and_mark!(@network_range)

    assert_equal 1, result[:matching_policies].length
    assert_equal 1, result[:generated_rules].length

    @network_range.reload
    assert @network_range.policies_evaluated_at > original_evaluated_at
  end

  test "self.evaluate_and_mark! handles nil network range" do
    result = WafPolicyMatcher.evaluate_and_mark!(nil)
    assert_equal({ matching_policies: [], generated_rules: [] }, result)
  end

  test "self.batch_process_network_ranges processes multiple ranges" do
    # Create multiple network ranges
    range1 = NetworkRange.create!(network: "192.168.1.0/24", country: "BR")
    range2 = NetworkRange.create!(network: "192.168.2.0/24", country: "US")

    # Create policies
    brazil_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")
    us_policy = WafPolicy.create_country_policy(['US'], 'deny', @user, "Block US")

    results = WafPolicyMatcher.batch_process_network_ranges([range1, range2])

    assert_equal 2, results.length
    assert_equal range1, results[0][:network_range]
    assert_equal range2, results[1][:network_range]
    assert_equal 1, results[0][:matching_policies].length
    assert_equal 1, results[1][:matching_policies].length
  end

  test "self.process_ranges_without_policy_rules finds ranges needing evaluation" do
    # Create range with intelligence but no rules
    intelligent_range = NetworkRange.create!(
      network: "192.168.1.0/24",
      country: "BR",
      asn: 12345
    )

    # Create range with no intelligence
    dumb_range = NetworkRange.create!(
      network: "192.168.2.0/24"
    )

    # Create range with existing rules
    range_with_rules = NetworkRange.create!(
      network: "192.168.3.0/24",
      country: "US"
    )
    policy = WafPolicy.create_country_policy(['US'], 'deny', @user, "Block US")
    Rule.create!(
      rule_type: "network",
      action: "deny",
      network_range: range_with_rules,
      waf_policy: policy,
      user: @user
    )

    results = WafPolicyMatcher.process_ranges_without_policy_rules(limit: 10)

    # Should only process the intelligent range without rules
    assert_equal 1, results.length
    assert_equal intelligent_range, results[0][:network_range]
  end

  test "self.reprocess_all_for_policy finds potential ranges for policy" do
    # Create country policy
    brazil_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")

    # Create matching and non-matching ranges
    brazil_range = NetworkRange.create!(network: "192.168.1.0/24", country: "BR")
    us_range = NetworkRange.create!(network: "192.168.2.0/24", country: "US")

    results = WafPolicyMatcher.reprocess_all_for_policy(brazil_policy)

    assert_equal 1, results.length
    assert_equal brazil_range, results[0][:network_range]
    assert_not_nil results[0][:generated_rule]
  end

  test "self.reprocess_all_for_policy handles different policy types" do
    # Test ASN policy
    asn_policy = WafPolicy.create_asn_policy([12345], 'deny', @user, "Block ASN")
    asn_range = NetworkRange.create!(network: "192.168.1.0/24", asn: 12345)

    # Test company policy
    company_policy = WafPolicy.create_company_policy(['Test Corp'], 'deny', @user, "Block Company")
    company_range = NetworkRange.create!(network: "192.168.2.0/24", company: "Test Corporation")

    # Test network type policy
    network_type_policy = WafPolicy.create_network_type_policy(['datacenter'], 'deny', @user, "Block Datacenter")
    dc_range = NetworkRange.create!(network: "192.168.3.0/24", is_datacenter: true)

    asn_results = WafPolicyMatcher.reprocess_all_for_policy(asn_policy)
    company_results = WafPolicyMatcher.reprocess_all_for_policy(company_policy)
    network_type_results = WafPolicyMatcher.reprocess_all_for_policy(network_type_policy)

    assert_equal 1, asn_results.length
    assert_equal 1, company_results.length
    assert_equal 1, network_type_results.length
  end

  # Statistics and Reporting
  test "self.matching_policies_for_network_range returns matching policies" do
    policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")

    matching_policies = WafPolicyMatcher.matching_policies_for_network_range(@network_range)

    assert_equal 1, matching_policies.length
    assert_includes matching_policies, policy
  end

  test "self.policy_effectiveness_stats returns correct statistics" do
    policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")

    # Create some rules for the policy
    range1 = NetworkRange.create!(network: "192.168.1.0/24", country: "BR")
    range2 = NetworkRange.create!(network: "192.168.2.0/24", country: "BR")

    rule1 = Rule.create!(
      rule_type: "network",
      action: "deny",
      network_range: range1,
      waf_policy: policy,
      user: @user
    )
    rule2 = Rule.create!(
      rule_type: "network",
      action: "deny",
      network_range: range2,
      waf_policy: policy,
      user: @user,
      enabled: false  # Disabled rule
    )

    stats = WafPolicyMatcher.policy_effectiveness_stats(policy, days: 30)

    assert_equal policy.name, stats[:policy_name]
    assert_equal "country", stats[:policy_type]
    assert_equal "deny", stats[:action]
    assert_equal 2, stats[:rules_generated]
    assert_equal 1, stats[:active_rules]  # Only enabled rules
    assert_equal 2, stats[:networks_protected]
    assert_equal 30, stats[:period_days]
    assert_equal 2.0 / 30, stats[:generation_rate]
  end

  # Network Intelligence Matching
  test "matches country policies based on network range country" do
    range_with_country = NetworkRange.create!(network: "192.168.1.0/24", country: "BR")
    range_without_country = NetworkRange.create!(network: "192.168.2.0/24")

    brazil_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")

    matcher1 = WafPolicyMatcher.new(network_range: range_with_country)
    matcher2 = WafPolicyMatcher.new(network_range: range_without_country)

    matching1 = matcher1.find_matching_policies
    matching2 = matcher2.find_matching_policies

    assert_includes matching1, brazil_policy
    assert_not_includes matching2, brazil_policy
  end

  test "matches network type policies based on intelligence flags" do
    dc_range = NetworkRange.create!(network: "192.168.1.0/24", is_datacenter: true)
    proxy_range = NetworkRange.create!(network: "192.168.2.0/24", is_proxy: true)
    standard_range = NetworkRange.create!(network: "192.168.3.0/24")  # All flags false

    dc_policy = WafPolicy.create_network_type_policy(['datacenter'], 'deny', @user, "Block Datacenter")
    proxy_policy = WafPolicy.create_network_type_policy(['proxy'], 'deny', @user, "Block Proxy")
    standard_policy = WafPolicy.create_network_type_policy(['standard'], 'deny', @user, "Block Standard")

    dc_matcher = WafPolicyMatcher.new(network_range: dc_range)
    proxy_matcher = WafPolicyMatcher.new(network_range: proxy_range)
    standard_matcher = WafPolicyMatcher.new(network_range: standard_range)

    assert_includes dc_matcher.find_matching_policies, dc_policy
    assert_includes proxy_matcher.find_matching_policies, proxy_policy
    assert_includes standard_matcher.find_matching_policies, standard_policy
  end

  # Inheritance Support
  test "matches policies based on inherited intelligence" do
    # Create parent network with intelligence
    parent = NetworkRange.create!(
      network: "192.168.0.0/16",
      country: "BR",
      company: "Test Corp"
    )

    # Create child network without its own intelligence
    child = NetworkRange.create!(network: "192.168.1.0/24")

    brazil_policy = WafPolicy.create_country_policy(['BR'], 'deny', @user, "Block Brazil")
    company_policy = WafPolicy.create_company_policy(['Test Corp'], 'challenge', @user, "Challenge Corp")

    matcher = WafPolicyMatcher.new(network_range: child)
    matching_policies = matcher.find_matching_policies

    # Should match based on inherited intelligence
    assert_includes matching_policies, brazil_policy
    assert_includes matching_policies, company_policy
  end

  # Performance and Edge Cases
  test "handles large numbers of policies efficiently" do
    # Create many policies
    policies = []
    100.times do |i|
      policies << WafPolicy.create_country_policy(
        ["US"], "deny", @user, "Policy #{i}"
      )
    end

    # Only one should match (our network is BR, not US)
    matching_policies = @matcher.find_matching_policies
    assert_equal 0, matching_policies.length
  end

  test "handles policies with complex additional_data" do
    redirect_policy = WafPolicy.create!(
      name: "Complex Redirect",
      policy_type: "country",
      targets: ["BR"],
      policy_action: "redirect",
      user: @user,
      additional_data: {
        "redirect_url" => "https://example.com/blocked",
        "redirect_status" => 301,
        "custom_headers" => {
          "X-Block-Reason" => "Country blocked"
        }
      }
    )

    rule = redirect_policy.create_rule_for_network_range(@network_range)

    assert_not_nil rule
    assert_equal "redirect", rule.waf_action
    assert rule.metadata['redirect_url'].present?
  end

  test "handles company name case-insensitive matching" do
    range = NetworkRange.create!(
      network: "192.168.1.0/24",
      company: "Google LLC"
    )

    # Policies with different case variations
    policy1 = WafPolicy.create_company_policy(['google'], 'deny', @user, "Block Google")
    policy2 = WafPolicy.create_company_policy(['GOOGLE LLC'], 'deny', @user, "Block Google LLC")
    policy3 = WafPolicy.create_company_policy(['Microsoft'], 'deny', @user, "Block Microsoft")

    matcher = WafPolicyMatcher.new(network_range: range)
    matching_policies = matcher.find_matching_policies

    assert_includes matching_policies, policy1
    assert_includes matching_policies, policy2
    assert_not_includes matching_policies, policy3
  end

  test "handles partial company name matching" do
    range = NetworkRange.create!(
      network: "192.168.1.0/24",
      company: "Amazon Web Services"
    )

    # Policy with partial match
    policy = WafPolicy.create_company_policy(['Amazon'], 'deny', @user, "Block Amazon")

    matcher = WafPolicyMatcher.new(network_range: range)
    matching_policies = matcher.find_matching_policies

    assert_includes matching_policies, policy
  end
end