defmodule Towerops.GeoIPTest do
  use Towerops.DataCase, async: true

  alias Towerops.GeoIP
  alias Towerops.GeoIP.Block
  alias Towerops.GeoIP.Location

  describe "lookup/1" do
    test "returns country code for valid IP with location data" do
      # Insert test location
      location = insert_location(%{geoname_id: 5_375_480, country_code: "US"})

      # Insert test block for IP 8.8.8.8 (134744072)
      insert_block(%{
        geoname_id: location.geoname_id,
        start_ip_int: 134_744_072,
        end_ip_int: 134_744_072
      })

      assert GeoIP.lookup("8.8.8.8") == "US"
    end

    test "returns nil for valid IP with no location data" do
      assert GeoIP.lookup("192.168.1.1") == nil
    end

    test "returns nil for invalid IP address" do
      assert GeoIP.lookup("invalid") == nil
    end

    test "returns nil for empty string" do
      assert GeoIP.lookup("") == nil
    end

    test "returns nil for IP with too many octets" do
      assert GeoIP.lookup("1.2.3.4.5") == nil
    end

    test "returns nil for IP with invalid octets" do
      assert GeoIP.lookup("999.999.999.999") == nil
    end
  end

  describe "lookup_full/1" do
    test "returns full location data for valid IP" do
      # Insert test location with full data
      location =
        insert_location(%{
          geoname_id: 5_375_480,
          country_code: "US",
          country_name: "United States",
          city_name: "Mountain View",
          subdivision_1_name: "California",
          subdivision_2_name: nil,
          latitude: 37.386,
          longitude: -122.0838
        })

      # Insert test block for IP 8.8.8.8
      insert_block(%{
        geoname_id: location.geoname_id,
        start_ip_int: 134_744_072,
        end_ip_int: 134_744_072
      })

      result = GeoIP.lookup_full("8.8.8.8")

      assert result.country_code == "US"
      assert result.country_name == "United States"
      assert result.city_name == "Mountain View"
      assert result.subdivision_1_name == "California"
      assert result.subdivision_2_name == nil
      assert result.latitude == 37.386
      assert result.longitude == -122.0838
    end

    test "returns location data with minimal fields" do
      location = insert_location(%{geoname_id: 1234, country_code: "CA"})

      insert_block(%{
        geoname_id: location.geoname_id,
        start_ip_int: 100,
        end_ip_int: 200
      })

      result = GeoIP.lookup_full("0.0.0.100")

      assert result.country_code == "CA"
      assert result.country_name == nil
      assert result.city_name == nil
    end

    test "returns nil for IP not in any block" do
      # Create location and block, but query different IP
      location = insert_location(%{geoname_id: 5678, country_code: "US"})

      insert_block(%{
        geoname_id: location.geoname_id,
        start_ip_int: 1000,
        end_ip_int: 2000
      })

      assert GeoIP.lookup_full("192.168.1.1") == nil
    end

    test "returns nil for invalid IP string" do
      assert GeoIP.lookup_full("not-an-ip") == nil
    end

    test "returns nil for empty string" do
      assert GeoIP.lookup_full("") == nil
    end

    test "handles IP at start of range" do
      location = insert_location(%{geoname_id: 9999, country_code: "FR"})

      insert_block(%{
        geoname_id: location.geoname_id,
        start_ip_int: 167_772_160,
        end_ip_int: 167_772_170
      })

      # 10.0.0.0 = 167772160
      result = GeoIP.lookup_full("10.0.0.0")
      assert result.country_code == "FR"
    end

    test "handles IP at end of range" do
      location = insert_location(%{geoname_id: 8888, country_code: "DE"})

      insert_block(%{
        geoname_id: location.geoname_id,
        start_ip_int: 167_772_160,
        end_ip_int: 167_772_170
      })

      # 10.0.0.10 = 167772170
      result = GeoIP.lookup_full("10.0.0.10")
      assert result.country_code == "DE"
    end

    test "handles IP in middle of range" do
      location = insert_location(%{geoname_id: 7777, country_code: "GB"})

      insert_block(%{
        geoname_id: location.geoname_id,
        start_ip_int: 167_772_160,
        end_ip_int: 167_772_170
      })

      # 10.0.0.5 = 167772165
      result = GeoIP.lookup_full("10.0.0.5")
      assert result.country_code == "GB"
    end

    test "returns first matching block when multiple blocks overlap" do
      location1 = insert_location(%{geoname_id: 1111, country_code: "US"})
      location2 = insert_location(%{geoname_id: 2222, country_code: "CA"})

      insert_block(%{
        network: "0.0.0.100/24",
        geoname_id: location1.geoname_id,
        start_ip_int: 100,
        end_ip_int: 200
      })

      insert_block(%{
        network: "0.0.0.50/24",
        geoname_id: location2.geoname_id,
        start_ip_int: 50,
        end_ip_int: 150
      })

      # Query returns first match due to limit: 1
      result = GeoIP.lookup_full("0.0.0.150")
      assert result.country_code in ["US", "CA"]
    end
  end

  # Helper functions to insert test data
  defp insert_location(attrs) do
    default_attrs = %{
      geoname_id: System.unique_integer([:positive]),
      country_code: "US",
      country_name: nil,
      city_name: nil,
      subdivision_1_name: nil,
      subdivision_2_name: nil,
      latitude: nil,
      longitude: nil
    }

    attrs = Map.merge(default_attrs, Map.new(attrs))

    %Location{}
    |> Location.changeset(attrs)
    |> Repo.insert!()
  end

  defp insert_block(attrs) do
    default_attrs = %{
      network: "0.0.0.0/32",
      start_ip_int: 0,
      end_ip_int: 0,
      geoname_id: nil,
      registered_country_geoname_id: nil
    }

    attrs = Map.merge(default_attrs, Map.new(attrs))

    %Block{}
    |> Block.changeset(attrs)
    |> Repo.insert!()
  end
end
