defmodule BerrypodWeb.Plugs.RateLimit do
  @moduledoc """
  Plug for rate limiting requests.

  ## Usage in router

      plug BerrypodWeb.Plugs.RateLimit, type: :login
      plug BerrypodWeb.Plugs.RateLimit, type: :api

  ## Types

  - `:login` - 5 requests per minute per IP (auth endpoints)
  - `:magic_link` - 3 requests per minute per email
  - `:newsletter` - 10 requests per minute per IP
  - `:api` - 60 requests per minute per IP
  """

  import Plug.Conn
  alias Berrypod.RateLimit

  def init(opts), do: opts

  def call(conn, opts) do
    # Skip rate limiting in test environment
    if Application.get_env(:berrypod, :env) == :test do
      conn
    else
      do_rate_limit(conn, opts[:type])
    end
  end

  defp do_rate_limit(conn, :login) do
    case RateLimit.check_login(conn.remote_ip) do
      :ok -> conn
      {:error, retry_after} -> rate_limited(conn, retry_after)
    end
  end

  defp do_rate_limit(conn, :magic_link) do
    # For magic link, we need to check the email from params
    # This is called after parsing, so we can access body params
    email = get_email_from_params(conn)

    if email do
      case RateLimit.check_magic_link(email) do
        :ok -> conn
        {:error, retry_after} -> rate_limited(conn, retry_after)
      end
    else
      # No email in request, let it through (will fail validation anyway)
      conn
    end
  end

  defp do_rate_limit(conn, :newsletter) do
    case RateLimit.check_newsletter(conn.remote_ip) do
      :ok -> conn
      {:error, retry_after} -> rate_limited(conn, retry_after)
    end
  end

  defp do_rate_limit(conn, :api) do
    case RateLimit.check_api(conn.remote_ip) do
      :ok -> conn
      {:error, retry_after} -> rate_limited(conn, retry_after)
    end
  end

  defp do_rate_limit(conn, _unknown) do
    conn
  end

  defp rate_limited(conn, retry_after_ms) do
    retry_after_seconds = max(div(retry_after_ms, 1000), 1)

    conn
    |> put_resp_header("retry-after", Integer.to_string(retry_after_seconds))
    |> put_status(:too_many_requests)
    |> Phoenix.Controller.put_view(BerrypodWeb.ErrorHTML)
    |> Phoenix.Controller.render("429.html")
    |> halt()
  end

  defp get_email_from_params(conn) do
    # Try common param structures for email
    conn.body_params["user"]["email"] ||
      conn.body_params["email"] ||
      conn.params["user"]["email"] ||
      conn.params["email"]
  end
end
