require "test_helper"

class NetworkRangeTest < ActiveSupport::TestCase
  setup do
    @ipv4_range = NetworkRange.new(network: "192.168.1.0/24")
    @ipv6_range = NetworkRange.new(network: "2001:db8::/32")
    @user = users(:jason)
  end

  # Validations
  test "should be valid with network address" do
    assert @ipv4_range.valid?
    assert @ipv6_range.valid?
  end

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

  test "should validate network uniqueness" do
    @ipv4_range.save!
    duplicate = NetworkRange.new(network: "192.168.1.0/24")
    assert_not duplicate.valid?
    assert_includes duplicate.errors[:network], "has already been taken"
  end

  test "should validate source inclusion" do
    valid_sources = %w[api_imported user_created manual auto_generated inherited geolite_asn geolite_country]
    valid_sources.each do |source|
      @ipv4_range.source = source
      assert @ipv4_range.valid?, "Source #{source} should be valid"
    end

    @ipv4_range.source = "invalid_source"
    assert_not @ipv4_range.valid?
    assert_includes @ipv4_range.errors[:source], "is not included in the list"
  end

  test "should validate ASN numericality" do
    @ipv4_range.asn = 12345
    assert @ipv4_range.valid?

    @ipv4_range.asn = 0
    assert_not @ipv4_range.valid?
    assert_includes @ipv4_range.errors[:asn], "must be greater than 0"

    @ipv4_range.asn = -1
    assert_not @ipv4_range.valid?
    assert_includes @ipv4_range.errors[:asn], "must be greater than 0"

    @ipv4_range.asn = "not_a_number"
    assert_not @ipv4_range.valid?

    @ipv4_range.asn = nil
    assert @ipv4_range.valid?
  end

  # Callbacks
  test "should set default source before validation" do
    range = NetworkRange.new(network: "10.0.0.0/8")
    range.valid?
    assert_equal "api_imported", range.source
  end

  test "should not override existing source" do
    range = NetworkRange.new(network: "10.0.0.0/8", source: "user_created")
    range.valid?
    assert_equal "user_created", range.source
  end

  # Virtual Attributes (CIDR)
  test "cidr getter returns network as string" do
    @ipv4_range.save!
    assert_equal "192.168.1.0/24", @ipv4_range.cidr
    assert_equal "192.168.1.0/24", @ipv4_range.network.to_s
  end

  test "cidr setter sets network from string" do
    range = NetworkRange.new
    range.cidr = "10.0.0.0/16"
    assert_equal "10.0.0.0/16", range.network.to_s
  end

  # Network Properties
  test "prefix_length returns correct network prefix" do
    @ipv4_range.save!
    @ipv6_range.save!

    assert_equal 24, @ipv4_range.prefix_length
    assert_equal 32, @ipv6_range.prefix_length
  end

  test "network_address returns network address" do
    @ipv4_range.save!
    assert_equal "192.168.1.0", @ipv4_range.network_address
  end

  test "broadcast_address returns correct broadcast for IPv4" do
    @ipv4_range.save!
    assert_equal "192.168.1.255", @ipv4_range.broadcast_address
  end

  test "broadcast_address returns nil for IPv6" do
    @ipv6_range.save!
    assert_nil @ipv6_range.broadcast_address
  end

  test "family detection works correctly" do
    @ipv4_range.save!
    @ipv6_range.save!

    assert_equal 4, @ipv4_range.family
    assert_equal 6, @ipv6_range.family
  end

  test "ipv4? and ipv6? predicate methods work" do
    @ipv4_range.save!
    @ipv6_range.save!

    assert @ipv4_range.ipv4?
    assert_not @ipv4_range.ipv6?

    assert @ipv6_range.ipv6?
    assert_not @ipv6_range.ipv4?
  end

  test "virtual? works correctly" do
    range = NetworkRange.new(network: "10.0.0.0/8")
    assert range.virtual?

    range.save!
    assert_not range.virtual?
  end

  # Network Containment
  test "contains_ip? works correctly" do
    @ipv4_range.save!

    assert @ipv4_range.contains_ip?("192.168.1.1")
    assert @ipv4_range.contains_ip?("192.168.1.254")
    assert_not @ipv4_range.contains_ip?("192.168.2.1")
    assert_not @ipv4_range.contains_ip?("2001:db8::1")

    # Test IPv6
    @ipv6_range.save!
    assert @ipv6_range.contains_ip?("2001:db8::1")
    assert @ipv6_range.contains_ip?("2001:db8:ffff::ffff")
    assert_not @ipv6_range.contains_ip?("2001:db9::1")
  end

  test "contains_network? works correctly" do
    @ipv4_range.save!

    # More specific network
    assert @ipv4_range.contains_network?("192.168.1.0/25")
    assert @ipv4_range.contains_network?("192.168.1.128/25")

    # Same network
    assert @ipv4_range.contains_network?("192.168.1.0/24")

    # Less specific network
    assert_not @ipv4_range.contains_network?("192.168.0.0/16")

    # Different network
    assert_not @ipv4_range.contains_network?("10.0.0.0/8")
  end

  test "overlaps? works correctly" do
    @ipv4_range.save!

    # Overlapping networks
    assert @ipv4_range.overlaps?("192.168.1.0/25") # More specific
    assert @ipv4_range.overlaps?("192.168.0.0/23") # Less specific
    assert @ipv4_range.overlaps?("192.168.1.128/25") # Partial overlap

    # Non-overlapping
    assert_not @ipv4_range.overlaps?("10.0.0.0/8")
    assert_not @ipv4_range.overlaps?("172.16.0.0/12")
  end

  # Parent/Child Relationships
  test "parent_ranges finds containing networks" do
    # Create parent and child networks
    parent = NetworkRange.create!(network: "192.168.0.0/16")
    @ipv4_range.save! # 192.168.1.0/24
    child = NetworkRange.create!(network: "192.168.1.0/25")

    parents = @ipv4_range.parent_ranges
    assert_includes parents, parent
    assert_not_includes parents, child
    assert_not_includes parents, @ipv4_range

    # Should be ordered by specificity (more specific first)
    assert_equal parent, parents.first
  end

  test "child_ranges finds contained networks" do
    # Create parent and child networks
    parent = NetworkRange.create!(network: "192.168.0.0/16")
    @ipv4_range.save! # 192.168.1.0/24
    child = NetworkRange.create!(network: "192.168.1.0/25")

    children = parent.child_ranges
    assert_includes children, @ipv4_range
    assert_includes children, child
    assert_not_includes children, parent

    # Should be ordered by specificity (less specific first)
    assert_equal @ipv4_range, children.first
  end

  test "sibling_ranges finds same-level networks" do
    # Create sibling networks
    sibling1 = NetworkRange.create!(network: "192.168.0.0/24")
    @ipv4_range.save! # 192.168.1.0/24
    sibling2 = NetworkRange.create!(network: "192.168.2.0/24")

    siblings = @ipv4_range.sibling_ranges
    assert_includes siblings, sibling1
    assert_includes siblings, sibling2
    assert_not_includes siblings, @ipv4_range
  end

  # Intelligence and Inheritance
  test "has_intelligence? detects available intelligence data" do
    range = NetworkRange.new(network: "10.0.0.0/8")
    assert_not range.has_intelligence?

    range.asn = 12345
    assert range.has_intelligence?

    range.asn = nil
    range.company = "Test Company"
    assert range.has_intelligence?

    range.company = nil
    range.country = "US"
    assert range.has_intelligence?

    range.country = nil
    range.is_datacenter = true
    assert range.has_intelligence?
  end

  test "own_intelligence returns correct data structure" do
    range = NetworkRange.create!(
      network: "10.0.0.0/8",
      asn: 12345,
      asn_org: "Test ASN",
      company: "Test Company",
      country: "US",
      is_datacenter: true,
      is_proxy: false,
      is_vpn: false,
      source: "manual"
    )

    intelligence = range.own_intelligence
    assert_equal 12345, intelligence[:asn]
    assert_equal "Test ASN", intelligence[:asn_org]
    assert_equal "Test Company", intelligence[:company]
    assert_equal "US", intelligence[:country]
    assert_equal true, intelligence[:is_datacenter]
    assert_equal false, intelligence[:is_proxy]
    assert_equal false, intelligence[:is_vpn]
    assert_equal false, intelligence[:inherited]
    assert_equal "manual", intelligence[:source]
  end

  test "inherited_intelligence returns own data when available" do
    child = NetworkRange.create!(
      network: "192.168.1.0/24",
      country: "US",
      company: "Test Company"
    )

    intelligence = child.inherited_intelligence
    assert_equal "US", intelligence[:country]
    assert_equal "Test Company", intelligence[:company]
    assert_equal false, intelligence[:inherited]
  end

  test "inherited_intelligence inherits from parent when needed" do
    parent = NetworkRange.create!(
      network: "192.168.0.0/16",
      country: "US",
      company: "Test Company"
    )
    child = NetworkRange.create!(network: "192.168.1.0/24")

    intelligence = child.inherited_intelligence
    assert_equal "US", intelligence[:country]
    assert_equal "Test Company", intelligence[:company]
    assert_equal true, intelligence[:inherited]
    assert_equal parent.cidr, intelligence[:parent_cidr]
  end

  test "parent_with_intelligence finds nearest parent with data" do
    grandparent = NetworkRange.create!(network: "10.0.0.0/8", country: "US")
    parent = NetworkRange.create!(network: "10.1.0.0/16")
    child = NetworkRange.create!(network: "10.1.1.0/24")

    found_parent = child.parent_with_intelligence
    assert_equal grandparent, found_parent
    assert_not_equal parent, found_parent
  end

  # API Data Management
  test "is_fetching_api_data? tracks active fetches" do
    range = NetworkRange.create!(network: "10.0.0.0/8")

    assert_not range.is_fetching_api_data?(:ipapi)

    range.mark_as_fetching_api_data!(:ipapi)
    assert range.is_fetching_api_data?(:ipapi)

    range.clear_fetching_status!(:ipapi)
    assert_not range.is_fetching_api_data?(:ipapi)
  end

  test "should_fetch_api_data? prevents duplicate fetches" do
    range = NetworkRange.create!(network: "10.0.0.0/8")

    # Should fetch initially
    assert range.should_fetch_api_data?(:ipapi)

    # Should not fetch while fetching
    range.mark_as_fetching_api_data!(:ipapi)
    assert_not range.should_fetch_api_data?(:ipapi)

    # Should fetch again after clearing
    range.clear_fetching_status!(:ipapi)
    assert range.should_fetch_api_data?(:ipapi)
  end

  test "has_ipapi_data_available? checks inheritance" do
    parent = NetworkRange.create!(network: "10.0.0.0/8")
    parent.set_network_data(:ipapi, { country: "US" })
    parent.save!

    child = NetworkRange.create!(network: "10.0.1.0/24")

    assert child.has_ipapi_data_available?
  end

  test "should_fetch_ipapi_data? respects parent fetching status" do
    parent = NetworkRange.create!(network: "10.0.0.0/8")
    child = NetworkRange.create!(network: "10.0.1.0/24")

    parent.mark_as_fetching_api_data!(:ipapi)
    assert_not child.should_fetch_ipapi_data?

    parent.clear_fetching_status!(:ipapi)
    assert child.should_fetch_ipapi_data?
  end

  # Network Data Management
  test "network_data_for and set_network_data work correctly" do
    range = NetworkRange.create!(network: "10.0.0.0/8")

    assert_equal({}, range.network_data_for(:ipapi))

    data = { country: "US", city: "New York" }
    range.set_network_data(:ipapi, data)
    range.save!

    assert_equal data, range.network_data_for(:ipapi)
  end

  test "has_network_data_from? checks data presence" do
    range = NetworkRange.create!(network: "10.0.0.0/8")

    assert_not range.has_network_data_from?(:ipapi)

    range.set_network_data(:ipapi, { country: "US" })
    range.save!

    assert range.has_network_data_from?(:ipapi)
  end

  # IPAPI Tracking Methods
  test "find_or_create_tracking_network_for_ip works correctly" do
    # IPv4 - should create /24
    tracking_range = NetworkRange.find_or_create_tracking_network_for_ip("192.168.1.100")
    assert_equal "192.168.1.0/24", tracking_range.network.to_s
    assert_equal "auto_generated", tracking_range.source
    assert_equal "IPAPI tracking network", tracking_range.creation_reason

    # IPv6 - should create /64
    ipv6_tracking = NetworkRange.find_or_create_tracking_network_for_ip("2001:db8::1")
    assert_equal "2001:db8::/64", ipv6_tracking.network.to_s
  end

  test "should_fetch_ipapi_for_ip? works correctly" do
    tracking_range = NetworkRange.create!(network: "192.168.1.0/8")

    # Should fetch initially
    assert NetworkRange.should_fetch_ipapi_for_ip?("192.168.1.100")

    # Mark as queried recently
    tracking_range.mark_ipapi_queried!("192.168.1.0/24")
    assert_not NetworkRange.should_fetch_ipapi_for_ip?("192.168.1.100")

    # Should fetch for old queries
    tracking_range.network_data['ipapi_queried_at'] = 2.years.ago.to_i
    tracking_range.save!
    assert NetworkRange.should_fetch_ipapi_for_ip?("192.168.1.100")
  end

  test "mark_ipapi_queried! stores query metadata" do
    range = NetworkRange.create!(network: "192.168.1.0/24")

    range.mark_ipapi_queried!("192.168.1.128/25")

    assert range.network_data['ipapi_queried_at'] > 5.seconds.ago.to_i
    assert_equal "192.168.1.128/25", range.network_data['ipapi_returned_cidr']
  end

  # JSON Helper Methods
  test "abuser_scores_hash handles JSON correctly" do
    range = NetworkRange.create!(network: "10.0.0.0/8")

    assert_equal({}, range.abuser_scores_hash)

    range.abuser_scores_hash = { ipquality: 85, abuseipdb: 92 }
    range.save!

    assert_equal({ "ipquality" => 85, "abuseipdb" => 92 }, JSON.parse(range.abuser_scores))
    assert_equal({ ipquality: 85, abuseipdb: 92 }, range.abuser_scores_hash)
  end

  test "additional_data_hash handles JSON correctly" do
    range = NetworkRange.create!(network: "10.0.0.0/8")

    assert_equal({}, range.additional_data_hash)

    range.additional_data_hash = { tags: ["malicious", "botnet"], notes: "Test data" }
    range.save!

    parsed_data = JSON.parse(range.additional_data)
    assert_equal ["malicious", "botnet"], parsed_data["tags"]
    assert_equal "Test data", parsed_data["notes"]
  end

  # Scopes
  test "ipv4 and ipv6 scopes work correctly" do
    ipv4_range = NetworkRange.create!(network: "192.168.1.0/24")
    ipv6_range = NetworkRange.create!(network: "2001:db8::/32")

    assert_includes NetworkRange.ipv4, ipv4_range
    assert_not_includes NetworkRange.ipv4, ipv6_range

    assert_includes NetworkRange.ipv6, ipv6_range
    assert_not_includes NetworkRange.ipv6, ipv4_range
  end

  test "filtering scopes work correctly" do
    range1 = NetworkRange.create!(network: "192.168.1.0/24", country: "US", company: "Google", asn: 15169, is_datacenter: true)
    range2 = NetworkRange.create!(network: "10.0.0.0/8", country: "BR", company: "Amazon", asn: 16509, is_proxy: true)

    assert_includes NetworkRange.by_country("US"), range1
    assert_not_includes NetworkRange.by_country("US"), range2

    assert_includes NetworkRange.by_company("Google"), range1
    assert_not_includes NetworkRange.by_company("Google"), range2

    assert_includes NetworkRange.by_asn(15169), range1
    assert_not_includes NetworkRange.by_asn(15169), range2

    assert_includes NetworkRange.datacenter, range1
    assert_not_includes NetworkRange.datacenter, range2

    assert_includes NetworkRange.proxy, range2
    assert_not_includes NetworkRange.proxy, range1
  end

  # Class Methods
  test "contains_ip class method finds most specific network" do
    parent = NetworkRange.create!(network: "192.168.0.0/16")
    child = NetworkRange.create!(network: "192.168.1.0/24")

    found = NetworkRange.contains_ip("192.168.1.100")
    assert_equal child, found.first  # More specific should come first
  end

  test "overlapping class method finds overlapping networks" do
    range1 = NetworkRange.create!(network: "192.168.0.0/16")
    range2 = NetworkRange.create!(network: "192.168.1.0/24")
    range3 = NetworkRange.create!(network: "10.0.0.0/8")

    overlapping = NetworkRange.overlapping("192.168.1.0/24")
    assert_includes overlapping, range1
    assert_includes overlapping, range2
    assert_not_includes overlapping, range3
  end

  test "find_or_create_by_cidr works correctly" do
    # Creates new record
    range = NetworkRange.find_or_create_by_cidr("10.0.0.0/8", user: @user, source: "manual")
    assert range.persisted?
    assert_equal "10.0.0.0/8", range.network.to_s
    assert_equal @user, range.user
    assert_equal "manual", range.source

    # Returns existing record
    existing = NetworkRange.find_or_create_by_cidr("10.0.0.0/8")
    assert_equal range.id, existing.id
  end

  test "find_by_ip_or_network handles both IP and network inputs" do
    range = NetworkRange.create!(network: "192.168.1.0/24")

    # Find by IP within range
    found_by_ip = NetworkRange.find_by_ip_or_network("192.168.1.100")
    assert_includes found_by_ip, range

    # Find by exact network
    found_by_network = NetworkRange.find_by_ip_or_network("192.168.1.0/24")
    assert_includes found_by_network, range

    # Return none for invalid input
    assert_equal NetworkRange.none, NetworkRange.find_by_ip_or_network("")
    assert_equal NetworkRange.none, NetworkRange.find_by_ip_or_network("invalid")
  end

  # Analytics Methods
  test "events_count returns counter cache value" do
    range = NetworkRange.create!(network: "192.168.1.0/24")

    assert_equal 0, range.events_count

    # Update counter cache manually for testing
    range.update_column(:events_count, 5)
    assert_equal 5, range.events_count
  end

  test "events method finds events within range" do
    range = NetworkRange.create!(network: "192.168.1.0/24")

    # Create test events
    matching_event = Event.create!(
      request_id: "test-1",
      timestamp: Time.current,
      payload: {},
      ip_address: "192.168.1.100"
    )
    non_matching_event = Event.create!(
      request_id: "test-2",
      timestamp: Time.current,
      payload: {},
      ip_address: "10.0.0.1"
    )

    found_events = range.events
    assert_includes found_events, matching_event
    assert_not_includes found_events, non_matching_event
  end

  test "blocking_rules and active_rules work correctly" do
    range = NetworkRange.create!(network: "192.168.1.0/24")

    blocking_rule = Rule.create!(
      rule_type: "network",
      action: "deny",
      network_range: range,
      user: @user,
      enabled: true
    )
    allow_rule = Rule.create!(
      rule_type: "network",
      action: "allow",
      network_range: range,
      user: @user,
      enabled: true
    )
    disabled_rule = Rule.create!(
      rule_type: "network",
      action: "deny",
      network_range: range,
      user: @user,
      enabled: false
    )

    assert_includes range.blocking_rules, blocking_rule
    assert_not_includes range.blocking_rules, allow_rule
    assert_not_includes range.blocking_rules, disabled_rule

    assert_includes range.active_rules, blocking_rule
    assert_includes range.active_rules, allow_rule
    assert_not_includes range.active_rules, disabled_rule
  end

  # Policy Evaluation
  test "needs_policy_evaluation? works correctly" do
    range = NetworkRange.create!(network: "192.168.1.0/24")

    # Should evaluate if never evaluated
    assert range.needs_policy_evaluation?

    # Should evaluate if policies updated since last evaluation
    range.update!(policies_evaluated_at: 1.hour.ago)
    WafPolicy.create!(name: "Test Policy", policy_type: "country", targets: ["US"], policy_action: "deny", user: @user)
    assert range.needs_policy_evaluation?

    # Should not evaluate if up to date
    range.update!(policies_evaluated_at: 5.minutes.ago)
    assert_not range.needs_policy_evaluation?
  end

  # String Representations
  test "to_s returns cidr" do
    @ipv4_range.save!
    assert_equal @ipv4_range.cidr, @ipv4_range.to_s
  end

  test "to_param parameterizes cidr" do
    @ipv4_range.save!
    assert_equal "192.168.1.0_24", @ipv4_range.to_param
  end

  # Geographic Lookup
  test "geo_lookup_country! updates country when available" do
    range = NetworkRange.create!(network: "8.8.8.0/24")  # Google's network

    # Mock GeoIpService
    GeoIpService.expects(:lookup_country).with("8.8.8.0").returns("US")

    range.geo_lookup_country!
    assert_equal "US", range.reload.country
  end

  test "geo_lookup_country! handles errors gracefully" do
    range = NetworkRange.create!(network: "192.168.1.0/24")

    # Mock GeoIpService to raise error
    GeoIpService.expects(:lookup_country).with("192.168.1.0").raises(StandardError.new("Service error"))

    # Should not raise error but log it
    assert_nothing_raised do
      range.geo_lookup_country!
    end

    assert_nil range.reload.country
  end

  # Stats Methods
  test "import_stats_by_source returns statistics" do
    NetworkRange.create!(network: "10.0.0.0/8", source: "manual")
    NetworkRange.create!(network: "192.168.1.0/24", source: "api_imported")
    NetworkRange.create!(network: "172.16.0.0/12", source: "api_imported")

    stats = NetworkRange.import_stats_by_source
    assert_equal 2, stats.count

    api_stats = stats.find { |s| s.source == "api_imported" }
    assert_equal 2, api_stats.count
  end

  test "geolite_coverage_stats returns detailed coverage information" do
    NetworkRange.create!(network: "10.0.0.0/8", source: "geolite_asn", asn: 12345)
    NetworkRange.create!(network: "192.168.1.0/24", source: "geolite_country", country: "US")
    NetworkRange.create!(network: "172.16.0.0/12", source: "geolite_asn", country: "BR")

    stats = NetworkRange.geolite_coverage_stats
    assert_equal 3, stats[:total_networks]
    assert_equal 2, stats[:asn_networks]
    assert_equal 1, stats[:country_networks]
    assert_equal 2, stats[:with_asn_data]
    assert_equal 1, stats[:with_country_data]
    assert_equal 2, stats[:unique_countries]
    assert_equal 2, stats[:unique_asns]
  end
end