defmodule Towerops.Geocoding do @moduledoc """ Service for geocoding addresses using Google Maps Geocoding API. Supports both system-wide API keys and organization-specific encrypted API keys. """ alias Towerops.HTTP require Logger @google_geocoding_url "https://maps.googleapis.com/maps/api/geocode/json" @doc """ Geocode an address to latitude/longitude coordinates. ## Parameters - `address`: The address string to geocode ## Returns - `{:ok, %{latitude: float, longitude: float, formatted_address: string}}` on success - `{:error, reason}` on failure """ def geocode(address) when is_binary(address) do with {:ok, api_key} <- get_api_key(), {:ok, response} <- make_geocoding_request(address, api_key) do parse_geocoding_response(response) end end defp get_api_key do api_key = get_system_api_key() if api_key && String.trim(api_key) != "" do {:ok, String.trim(api_key)} else {:error, :no_api_key} end end defp get_system_api_key do Application.get_env(:towerops, :google_maps_api_key) || System.get_env("GOOGLE_MAPS_API_KEY") end defp make_geocoding_request(address, api_key) do params = %{ "address" => address, "key" => api_key } case HTTP.get(__MODULE__, @google_geocoding_url, params: params, timeout: 10_000) do {:ok, %Req.Response{status: 200, body: body}} -> {:ok, body} {:ok, %Req.Response{status: status_code, body: body}} -> Logger.warning("Geocoding API returned status #{status_code}: #{inspect(body)}") {:error, "Geocoding API error (status #{status_code})"} {:error, reason} -> Logger.error("Failed to make geocoding request: #{inspect(reason)}") {:error, "Network error: #{inspect(reason)}"} end end defp parse_geocoding_response(%{"status" => "OK", "results" => [result | _]}) do with %{"geometry" => %{"location" => %{"lat" => lat, "lng" => lng}}} <- result, %{"formatted_address" => formatted_address} <- result do {:ok, %{ latitude: lat, longitude: lng, formatted_address: formatted_address }} else _ -> {:error, "Invalid response format from Google Maps API"} end end defp parse_geocoding_response(%{"status" => "ZERO_RESULTS"}) do {:error, "No results found for the given address"} end defp parse_geocoding_response(%{"status" => "OVER_QUERY_LIMIT"}) do {:error, "API quota exceeded. Please check your Google Maps API usage."} end defp parse_geocoding_response(%{"status" => "REQUEST_DENIED", "error_message" => message}) do {:error, "API request denied: #{message}"} end defp parse_geocoding_response(%{"status" => "INVALID_REQUEST"}) do {:error, "Invalid request. Please check the address format."} end defp parse_geocoding_response(%{"status" => status}) do {:error, "Geocoding failed with status: #{status}"} end defp parse_geocoding_response(_response) do {:error, "Unexpected response format from Google Maps API"} end end