defmodule Towerops.MobileSessions do
  @moduledoc """
  Context for managing mobile app authentication sessions.
  """

  import Ecto.Query

  alias Towerops.MobileSessions.MobileSession
  alias Towerops.MobileSessions.QRLoginToken
  alias Towerops.Repo

  @doc """
  Creates a new mobile session for a user.

  ## Examples

      iex> create_mobile_session(%{user_id: user.id, device_name: "iPhone 15"})
      {:ok, %MobileSession{}}
  """
  def create_mobile_session(attrs) do
    %MobileSession{}
    |> MobileSession.create_changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Gets a mobile session by ID.
  """
  def get_session(session_id) do
    Repo.get(MobileSession, session_id)
  end

  @doc """
  Gets a mobile session by token.

  Returns nil if the session is expired or doesn't exist.
  """
  def get_session_by_token(token) when is_binary(token) do
    now = DateTime.utc_now()

    MobileSession
    |> where([s], s.token == ^token)
    |> where([s], s.expires_at > ^now)
    |> Repo.one()
  end

  def get_session_by_token(_), do: nil

  @doc """
  Updates the last_used_at timestamp for a mobile session.
  """
  def touch_session(%MobileSession{} = session) do
    session
    |> MobileSession.touch_changeset()
    |> Repo.update()
  end

  @doc """
  Lists all active mobile sessions for a user.
  """
  def list_user_sessions(user_id) do
    now = DateTime.utc_now()

    MobileSession
    |> where([s], s.user_id == ^user_id)
    |> where([s], s.expires_at > ^now)
    |> order_by([s], desc: s.last_used_at)
    |> Repo.all()
  end

  @doc """
  Revokes a mobile session by ID.
  """
  def revoke_session(session_id) do
    case Repo.get(MobileSession, session_id) do
      nil -> {:error, :not_found}
      session -> Repo.delete(session)
    end
  end

  @doc """
  Updates alert preferences for a mobile session.
  """
  def update_alert_preferences(session_id, attrs) do
    case Repo.get(MobileSession, session_id) do
      nil ->
        {:error, :not_found}

      session ->
        session
        |> Ecto.Changeset.cast(attrs, [:alerts_enabled, :push_token, :push_platform])
        |> Repo.update()
    end
  end

  @doc """
  Lists all devices with alerts enabled for a user.
  Used for sending push notifications.
  """
  def list_alert_enabled_devices(user_id) do
    now = DateTime.utc_now()

    MobileSession
    |> where([s], s.user_id == ^user_id)
    |> where([s], s.alerts_enabled == true)
    |> where([s], s.expires_at > ^now)
    |> where([s], not is_nil(s.push_token))
    |> Repo.all()
  end

  @doc """
  Revokes all mobile sessions for a user.
  """
  def revoke_all_user_sessions(user_id) do
    {count, _} =
      MobileSession
      |> where([s], s.user_id == ^user_id)
      |> Repo.delete_all()

    {:ok, count}
  end

  @doc """
  Deletes expired mobile sessions.

  Returns the number of sessions deleted.
  """
  def delete_expired_sessions do
    now = DateTime.utc_now()

    {count, _} =
      MobileSession
      |> where([s], s.expires_at <= ^now)
      |> Repo.delete_all()

    count
  end

  # QR Login Token functions

  @doc """
  Creates a new QR login token for a user.

  Token expires in 5 minutes.
  """
  def create_qr_login_token(user_id) do
    %QRLoginToken{}
    |> QRLoginToken.create_changeset(%{user_id: user_id})
    |> Repo.insert()
  end

  @doc """
  Gets a QR login token by token string.

  Returns nil if the token is expired, already used, or doesn't exist.
  """
  def get_qr_login_token(token) when is_binary(token) do
    now = DateTime.utc_now()

    QRLoginToken
    |> where([t], t.token == ^token)
    |> where([t], t.expires_at > ^now)
    |> where([t], is_nil(t.completed_at))
    |> Repo.one()
  end

  def get_qr_login_token(_), do: nil

  @doc """
  Completes a QR login token by creating a mobile session.

  Returns {:ok, mobile_session} on success.
  """
  def complete_qr_login(token, device_attrs) do
    Repo.transaction(fn ->
      with {:ok, qr_token} <- fetch_qr_token(token),
           session_attrs = Map.put(device_attrs, :user_id, qr_token.user_id),
           {:ok, session} <- create_mobile_session(session_attrs) do
        # Mark QR token as completed
        qr_token
        |> QRLoginToken.complete_changeset(session.id)
        |> Repo.update!()

        session
      else
        {:error, :invalid_token} -> Repo.rollback(:invalid_token)
        {:error, changeset} -> Repo.rollback(changeset)
      end
    end)
  end

  defp fetch_qr_token(token) do
    case get_qr_login_token(token) do
      nil -> {:error, :invalid_token}
      qr_token -> {:ok, qr_token}
    end
  end

  @doc """
  Deletes expired QR login tokens.

  Returns the number of tokens deleted.
  """
  def delete_expired_qr_tokens do
    now = DateTime.utc_now()

    {count, _} =
      QRLoginToken
      |> where([t], t.expires_at <= ^now)
      |> Repo.delete_all()

    count
  end

  @doc """
  Checks if a QR login token has been completed.

  Returns the mobile session if completed, nil otherwise.
  """
  def check_qr_login_completed(token) when is_binary(token) do
    QRLoginToken
    |> where([t], t.token == ^token)
    |> where([t], not is_nil(t.completed_at))
    |> preload(:mobile_session)
    |> Repo.one()
    |> case do
      %QRLoginToken{mobile_session: session} when not is_nil(session) -> session
      _ -> nil
    end
  end

  def check_qr_login_completed(_), do: nil
end
