require "test_helper"

class WafPolicyTest < ActiveSupport::TestCase
  setup do
    @user = users(:jason)
    @policy = WafPolicy.new(
      name: "Block Malicious IPs",
      policy_type: "country",
      targets: ["BR", "CN"],
      policy_action: "deny",
      user: @user
    )
  end

  # Validations
  test "should be valid with all required attributes" do
    assert @policy.valid?
  end

  test "should not be valid without name" do
    @policy.name = nil
    assert_not @policy.valid?
    assert_includes @policy.errors[:name], "can't be blank"
  end

  test "should not be valid without unique name" do
    @policy.name = waf_policies(:one).name
    assert_not @policy.valid?
    assert_includes @policy.errors[:name], "has already been taken"
  end

  test "should validate policy_type inclusion" do
    @policy.policy_type = "invalid_type"
    assert_not @policy.valid?
    assert_includes @policy.errors[:policy_type], "is not included in the list"
  end

  test "should validate policy_action inclusion" do
    @policy.policy_action = "invalid_action"
    assert_not @policy.valid?
    assert_includes @policy.errors[:policy_action], "is not included in the list"
  end

  test "should not be valid without targets" do
    @policy.targets = []
    assert_not @policy.valid?
    assert_includes @policy.errors[:targets], "can't be blank"
  end

  test "should validate targets is an array" do
    @policy.targets = "not an array"
    assert_not @policy.valid?
    assert_includes @policy.errors[:targets], "must be an array"
  end

  test "should validate country targets format" do
    @policy.policy_type = "country"

    # Valid country codes
    @policy.targets = ["US", "BR", "CN"]
    assert @policy.valid?

    # Invalid country codes
    @policy.targets = ["USA", "123", "B"]
    assert_not @policy.valid?
    assert_includes @policy.errors[:targets], "must be valid ISO country codes"
  end

  test "should validate ASN targets format" do
    @policy.policy_type = "asn"

    # Valid ASNs
    @policy.targets = [12345, 67890]
    assert @policy.valid?

    # Invalid ASNs
    @policy.targets = ["AS12345", -1, 0]
    assert_not @policy.valid?
    assert_includes @policy.errors[:targets], "must be valid ASNs"
  end

  test "should validate company targets format" do
    @policy.policy_type = "company"

    # Valid company names
    @policy.targets = ["Google", "Amazon Web Services", "Microsoft"]
    assert @policy.valid?

    # Invalid company names
    @policy.targets = ["", nil, "   "]
    assert_not @policy.valid?
    assert_includes @policy.errors[:targets], "must be valid company names"
  end

  test "should validate network_type targets format" do
    @policy.policy_type = "network_type"

    # Valid network types
    @policy.targets = ["datacenter", "proxy", "vpn", "standard"]
    assert @policy.valid?

    # Invalid network types
    @policy.targets = ["invalid", "malicious", "botnet"]
    assert_not @policy.valid?
    assert_includes @policy.errors[:targets], "must be one of: datacenter, proxy, vpn, standard"
  end

  test "should validate redirect configuration" do
    @policy.policy_action = "redirect"

    # Valid redirect config
    @policy.additional_data = { "redirect_url" => "https://example.com/blocked" }
    assert @policy.valid?

    # Missing redirect URL
    @policy.additional_data = { "other_config" => "value" }
    assert_not @policy.valid?
    assert_includes @policy.errors[:additional_data], "must include 'redirect_url' for redirect action"
  end

  test "should validate challenge configuration" do
    @policy.policy_action = "challenge"

    # Valid challenge types
    @policy.additional_data = { "challenge_type" => "captcha" }
    assert @policy.valid?

    @policy.additional_data = { "challenge_type" => "javascript" }
    assert @policy.valid?

    # Invalid challenge type
    @policy.additional_data = { "challenge_type" => "invalid" }
    assert_not @policy.valid?
    assert_includes @policy.errors[:additional_data], "challenge_type must be one of: captcha, javascript, proof_of_work"

    # No challenge type (should be valid, uses defaults)
    @policy.additional_data = {}
    assert @policy.valid?
  end

  # Defaults and Callbacks
  test "should default to enabled" do
    @policy.enabled = nil
    @policy.save!
    assert @policy.enabled?
  end

  test "should default targets to empty array" do
    policy = WafPolicy.new(
      name: "Test Policy",
      policy_type: "country",
      policy_action: "deny",
      user: @user
    )
    # before_validation should set defaults
    policy.valid?
    assert_equal [], policy.targets
  end

  test "should default additional_data to empty hash" do
    policy = WafPolicy.new(
      name: "Test Policy",
      policy_type: "country",
      targets: ["US"],
      policy_action: "deny",
      user: @user
    )
    policy.valid?
    assert_equal({}, policy.additional_data)
  end

  # Policy Type Methods
  test "policy type predicate methods work correctly" do
    country_policy = WafPolicy.new(policy_type: "country")
    assert country_policy.country_policy?
    assert_not country_policy.asn_policy?
    assert_not country_policy.company_policy?
    assert_not country_policy.network_type_policy?

    asn_policy = WafPolicy.new(policy_type: "asn")
    assert_not asn_policy.country_policy?
    assert asn_policy.asn_policy?
    assert_not asn_policy.company_policy?
    assert_not asn_policy.network_type_policy?

    company_policy = WafPolicy.new(policy_type: "company")
    assert_not company_policy.country_policy?
    assert_not company_policy.asn_policy?
    assert company_policy.company_policy?
    assert_not company_policy.network_type_policy?

    network_type_policy = WafPolicy.new(policy_type: "network_type")
    assert_not network_type_policy.country_policy?
    assert_not network_type_policy.asn_policy?
    assert_not network_type_policy.company_policy?
    assert network_type_policy.network_type_policy?
  end

  # Action Methods
  test "action predicate methods work correctly" do
    allow_policy = WafPolicy.new(policy_action: "allow")
    assert allow_policy.allow_action?
    assert_not allow_policy.deny_action?
    assert_not allow_policy.redirect_action?
    assert_not allow_policy.challenge_action?

    deny_policy = WafPolicy.new(policy_action: "deny")
    assert_not deny_policy.allow_action?
    assert deny_policy.deny_action?
    assert_not deny_policy.redirect_action?
    assert_not deny_policy.challenge_action?

    redirect_policy = WafPolicy.new(policy_action: "redirect")
    assert_not redirect_policy.allow_action?
    assert_not redirect_policy.deny_action?
    assert redirect_policy.redirect_action?
    assert_not redirect_policy.challenge_action?

    challenge_policy = WafPolicy.new(policy_action: "challenge")
    assert_not challenge_policy.allow_action?
    assert_not challenge_policy.deny_action?
    assert_not challenge_policy.redirect_action?
    assert challenge_policy.challenge_action?
  end

  # Policy action methods (to avoid Rails conflicts)
  test "policy action predicate methods work correctly" do
    policy = WafPolicy.new(policy_action: "deny")
    assert policy.deny_policy_action?
    assert_not policy.allow_policy_action?
    assert_not policy.redirect_policy_action?
    assert_not policy.challenge_policy_action?
  end

  # Lifecycle Methods
  test "active? works correctly" do
    # Active policy
    active_policy = WafPolicy.new(enabled: true, expires_at: nil)
    assert active_policy.active?

    # Enabled but expired
    expired_policy = WafPolicy.new(enabled: true, expires_at: 1.day.ago)
    assert_not expired_policy.active?

    # Disabled with future expiration
    disabled_policy = WafPolicy.new(enabled: false, expires_at: 1.day.from_now)
    assert_not disabled_policy.active?

    # Disabled with no expiration
    disabled_no_exp = WafPolicy.new(enabled: false, expires_at: nil)
    assert_not disabled_no_exp.active?

    # Enabled with future expiration
    future_exp = WafPolicy.new(enabled: true, expires_at: 1.day.from_now)
    assert future_exp.active?
  end

  test "expired? works correctly" do
    assert_not WafPolicy.new(expires_at: nil).expired?
    assert_not WafPolicy.new(expires_at: 1.day.from_now).expired?
    assert WafPolicy.new(expires_at: 1.day.ago).expired?
    assert WafPolicy.new(expires_at: Time.current).expired?
  end

  test "activate! enables policy" do
    @policy.enabled = false
    @policy.save!

    @policy.activate!
    assert @policy.reload.enabled?
  end

  test "deactivate! disables policy" do
    @policy.enabled = true
    @policy.save!

    @policy.deactivate!
    assert_not @policy.reload.enabled?
  end

  test "expire! sets expiration to now" do
    @policy.expire!
    assert @policy.reload.expires_at <= Time.current
  end

  # Scopes
  test "enabled scope returns only enabled policies" do
    enabled_policy = WafPolicy.create!(
      name: "Enabled Policy",
      policy_type: "country",
      targets: ["US"],
      policy_action: "deny",
      user: @user,
      enabled: true
    )
    disabled_policy = WafPolicy.create!(
      name: "Disabled Policy",
      policy_type: "country",
      targets: ["US"],
      policy_action: "deny",
      user: @user,
      enabled: false
    )

    enabled_policies = WafPolicy.enabled
    assert_includes enabled_policies, enabled_policy
    assert_not_includes enabled_policies, disabled_policy
  end

  test "active scope returns only active policies" do
    active_policy = WafPolicy.create!(
      name: "Active Policy",
      policy_type: "country",
      targets: ["US"],
      policy_action: "deny",
      user: @user,
      enabled: true,
      expires_at: 1.day.from_now
    )
    expired_policy = WafPolicy.create!(
      name: "Expired Policy",
      policy_type: "country",
      targets: ["US"],
      policy_action: "deny",
      user: @user,
      enabled: true,
      expires_at: 1.day.ago
    )
    disabled_policy = WafPolicy.create!(
      name: "Disabled Policy",
      policy_type: "country",
      targets: ["US"],
      policy_action: "deny",
      user: @user,
      enabled: false
    )

    active_policies = WafPolicy.active
    assert_includes active_policies, active_policy
    assert_not_includes active_policies, expired_policy
    assert_not_includes active_policies, disabled_policy
  end

  # Class Factory Methods
  test "create_country_policy works correctly" do
    policy = WafPolicy.create_country_policy(
      ["US", "CA"],
      policy_action: "deny",
      user: @user,
      name: "Custom Name"
    )

    assert policy.persisted?
    assert_equal "Custom Name", policy.name
    assert_equal "country", policy.policy_type
    assert_equal "deny", policy.policy_action
    assert_equal ["US", "CA"], policy.targets
    assert_equal @user, policy.user
  end

  test "create_asn_policy works correctly" do
    policy = WafPolicy.create_asn_policy(
      [12345, 67890],
      policy_action: "challenge",
      user: @user
    )

    assert policy.persisted?
    assert_equal "challenge ASNs 12345, 67890", policy.name
    assert_equal "asn", policy.policy_type
    assert_equal "challenge", policy.policy_action
    assert_equal [12345, 67890], policy.targets
  end

  test "create_company_policy works correctly" do
    policy = WafPolicy.create_company_policy(
      ["Google", "Amazon"],
      policy_action: "deny",
      user: @user
    )

    assert policy.persisted?
    assert_equal "deny Google, Amazon", policy.name
    assert_equal "company", policy.policy_type
    assert_equal ["Google", "Amazon"], policy.targets
  end

  test "create_network_type_policy works correctly" do
    policy = WafPolicy.create_network_type_policy(
      ["datacenter", "proxy"],
      policy_action: "redirect",
      user: @user,
      additional_data: { redirect_url: "https://example.com/blocked" }
    )

    assert policy.persisted?
    assert_equal "redirect datacenter, proxy", policy.name
    assert_equal "network_type", policy.policy_type
    assert_equal ["datacenter", "proxy"], policy.targets
  end

  # Redirect/Challenge Specific Methods
  test "redirect_url and redirect_status methods work" do
    policy = WafPolicy.new(
      policy_action: "redirect",
      additional_data: {
        "redirect_url" => "https://example.com/blocked",
        "redirect_status" => 301
      }
    )

    assert_equal "https://example.com/blocked", policy.redirect_url
    assert_equal 301, policy.redirect_status

    # Default status
    policy.additional_data = { "redirect_url" => "https://example.com/blocked" }
    assert_equal 302, policy.redirect_status
  end

  test "challenge_type and challenge_message methods work" do
    policy = WafPolicy.new(
      policy_action: "challenge",
      additional_data: {
        "challenge_type" => "javascript",
        "challenge_message" => "Please verify you are human"
      }
    )

    assert_equal "javascript", policy.challenge_type
    assert_equal "Please verify you are human", policy.challenge_message

    # Default challenge type
    policy.additional_data = {}
    assert_equal "captcha", policy.challenge_type
  end

  # Statistics
  test "generated_rules_count works" do
    @policy.save!

    # Initially no rules
    assert_equal 0, @policy.generated_rules_count

    # Create some rules
    network_range = NetworkRange.create!(ip_range: "192.168.1.0/24")
    @policy.create_rule_for_network_range(network_range)

    assert_equal 1, @policy.generated_rules_count
  end

  test "effectiveness_stats returns correct data" do
    @policy.save!

    stats = @policy.effectiveness_stats

    assert_equal 0, stats[:total_rules_generated]
    assert_equal 0, stats[:active_rules]
    assert_equal 0, stats[:rules_last_7_days]
    assert_equal "country", stats[:policy_type]
    assert_equal "deny", stats[:policy_action]
    assert_equal 2, stats[:targets_count]
  end

  # String representations
  test "to_s returns name" do
    assert_equal @policy.name, @policy.to_s
  end

  test "to_param parameterizes name" do
    @policy.name = "Block Brazil & China"
    expected = "block-brazil-china"
    assert_equal expected, @policy.to_param
  end
end
