defmodule Berrypod.RateLimit do
  @moduledoc """
  Rate limiting using Hammer with ETS backend.

  Provides protection against brute force attacks on auth endpoints
  and DoS attacks on API endpoints.

  ## Limits

  - Login attempts: 5 per minute per IP
  - Magic link requests: 3 per minute per email
  - Newsletter signup: 10 per minute per IP
  - API requests: 60 per minute per IP

  ## Usage

      case Berrypod.RateLimit.check_login(ip_address) do
        :ok -> proceed_with_login()
        {:error, retry_after_ms} -> return_429(retry_after_ms)
      end
  """

  use Hammer, backend: :ets

  # Limits (scale in milliseconds, limit is max hits per scale)
  @login_scale :timer.minutes(1)
  @login_limit 5

  @magic_link_scale :timer.minutes(1)
  @magic_link_limit 3

  @newsletter_scale :timer.minutes(1)
  @newsletter_limit 10

  @api_scale :timer.minutes(1)
  @api_limit 60

  @doc """
  Check rate limit for login attempts.
  Returns :ok or {:error, retry_after_ms}
  """
  def check_login(ip) do
    key = "login:#{format_ip(ip)}"
    check(key, @login_scale, @login_limit)
  end

  @doc """
  Check rate limit for magic link requests.
  Uses email as key to prevent enumeration attacks.
  """
  def check_magic_link(email) do
    key = "magic_link:#{email}"
    check(key, @magic_link_scale, @magic_link_limit)
  end

  @doc """
  Check rate limit for newsletter signups.
  """
  def check_newsletter(ip) do
    key = "newsletter:#{format_ip(ip)}"
    check(key, @newsletter_scale, @newsletter_limit)
  end

  @doc """
  Check rate limit for API requests.
  """
  def check_api(ip) do
    key = "api:#{format_ip(ip)}"
    check(key, @api_scale, @api_limit)
  end

  defp check(key, scale, limit) do
    case hit(key, scale, limit) do
      {:allow, _count} -> :ok
      {:deny, retry_after} -> {:error, retry_after}
    end
  end

  defp format_ip(ip) when is_tuple(ip), do: :inet.ntoa(ip) |> to_string()
  defp format_ip(ip) when is_binary(ip), do: ip
end
