defmodule ToweropsWeb.Plugs.UpdateSessionActivity do
  @moduledoc """
  Updates the last_activity_at timestamp for the current browser session on each request.

  This plug tracks session activity and enforces an inactivity timeout of 30 minutes.
  If a session has been inactive for longer than the timeout, the user is automatically
  logged out for security purposes.

  The activity timestamp is used in the Sessions tab of User Settings to show when
  each session was last active. The update happens in a background task to avoid
  slowing down requests.
  """
  import Phoenix.Controller, only: [put_flash: 3, redirect: 2]
  import Plug.Conn

  alias Towerops.Accounts

  # 30 minutes of inactivity before automatic logout
  @inactivity_timeout_seconds 30 * 60

  @doc """
  Initializes the plug with options (currently unused).
  """
  def init(opts), do: opts

  @doc """
  Updates the last_activity_at timestamp for the current browser session.

  Checks for session inactivity timeout and logs out the user if the session
  has been inactive for more than 30 minutes.

  Looks up the session by the token stored in the session cookie, then
  updates the timestamp in a background task.
  """
  def call(conn, _opts) do
    case get_session(conn, :user_token) do
      nil -> conn
      token -> handle_session_activity(conn, token)
    end
  end

  # Handle session activity check and timeout
  defp handle_session_activity(conn, token) do
    case check_session_activity(token) do
      :active ->
        # Session is active, update timestamp in background
        Task.start(fn -> update_session_activity(token) end)
        conn

      :timeout ->
        # Session timed out due to inactivity
        logout_inactive_session(conn, token)

      :not_found ->
        # Session not found, continue normally
        conn
    end
  end

  # Check if session is still active or has timed out
  defp check_session_activity(token) do
    case Accounts.get_browser_session_by_token_value(token) do
      nil ->
        :not_found

      session ->
        if session_timed_out?(session) do
          :timeout
        else
          :active
        end
    end
  end

  # Check if session has exceeded inactivity timeout
  defp session_timed_out?(session) do
    last_activity = session.last_activity_at || session.inserted_at
    inactive_seconds = DateTime.diff(DateTime.utc_now(), last_activity, :second)
    inactive_seconds > @inactivity_timeout_seconds
  end

  # Log out user due to inactivity timeout
  defp logout_inactive_session(conn, token) do
    # Delete the session token
    Accounts.delete_user_session_token(token)

    # Broadcast logout to LiveView socket
    if live_socket_id = get_session(conn, :live_socket_id) do
      ToweropsWeb.Endpoint.broadcast(live_socket_id, "disconnect", %{})
    end

    # Clear session and redirect to login
    conn
    |> configure_session(renew: true)
    |> clear_session()
    |> delete_resp_cookie("_towerops_web_user_remember_me")
    |> put_flash(:info, "Your session expired due to inactivity. Please log in again.")
    |> redirect(to: "/users/log-in")
    |> halt()
  end

  defp update_session_activity(token) do
    case Accounts.get_browser_session_by_token_value(token) do
      nil ->
        # No browser session found (might be an old session from before this feature)
        :ok

      session ->
        # Update activity timestamp
        Accounts.touch_browser_session(session)
    end
  end
end
