defmodule Towerops.GeoIP do
  @moduledoc """
  IP geolocation lookup using PostgreSQL database.

  Provides fast lookups for country and city information from IP addresses
  using the MaxMind GeoLite2-City database stored in PostgreSQL.
  """
  import Ecto.Query

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

  @doc """
  Looks up the country code for an IP address.

  Returns the 2-letter country code or nil if not found.

  ## Examples

      iex> Towerops.GeoIP.lookup("8.8.8.8")
      "US"

      iex> Towerops.GeoIP.lookup("invalid")
      nil
  """
  def lookup(ip_string) when is_binary(ip_string) do
    case lookup_full(ip_string) do
      %{country_code: country_code} -> country_code
      nil -> nil
    end
  end

  @doc """
  Looks up full location information for an IP address.

  Returns a map with country_code, country_name, city_name, and region information,
  or nil if not found.

  ## Examples

      iex> Towerops.GeoIP.lookup_full("8.8.8.8")
      %{
        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
      }

      iex> Towerops.GeoIP.lookup_full("invalid")
      nil
  """
  def lookup_full(ip_string) when is_binary(ip_string) do
    case parse_ip(ip_string) do
      {:ok, ip_int} ->
        find_location(ip_int)

      :error ->
        nil
    end
  end

  defp find_location(ip_int) do
    # Query for IP block that contains this IP address
    # Uses index on (start_ip_int, end_ip_int) for fast range lookup
    query =
      from b in Block,
        join: l in Location,
        on: b.geoname_id == l.geoname_id,
        where: b.start_ip_int <= ^ip_int and b.end_ip_int >= ^ip_int,
        select: %{
          country_code: l.country_code,
          country_name: l.country_name,
          city_name: l.city_name,
          subdivision_1_name: l.subdivision_1_name,
          subdivision_2_name: l.subdivision_2_name,
          latitude: l.latitude,
          longitude: l.longitude
        },
        limit: 1

    Repo.one(query)
  end

  defp parse_ip(ip_string) do
    case :inet.parse_address(String.to_charlist(ip_string)) do
      {:ok, {a, b, c, d}} ->
        # Convert IP to integer for range comparison
        ip_int = a * 256 * 256 * 256 + b * 256 * 256 + c * 256 + d
        {:ok, ip_int}

      _ ->
        :error
    end
  end
end
