defmodule Towerops.MobileSessions.MobileSession do
  @moduledoc """
  Schema for mobile app sessions.

  Long-lived authentication tokens for mobile apps. Users can manage
  their active mobile sessions from the web interface.
  """
  use Ecto.Schema

  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  schema "mobile_sessions" do
    field :token, :string
    field :device_name, :string
    field :device_os, :string
    field :app_version, :string
    field :last_used_at, :utc_datetime
    field :expires_at, :utc_datetime
    field :alerts_enabled, :boolean, default: true
    field :push_token, :string
    field :push_platform, :string

    belongs_to :user, Towerops.Accounts.User

    timestamps(type: :utc_datetime)
  end

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

  Token is automatically generated if not provided.
  Default expiration is 90 days from now.
  """
  def create_changeset(mobile_session, attrs) do
    mobile_session
    |> cast(attrs, [
      :user_id,
      :device_name,
      :device_os,
      :app_version,
      :token,
      :expires_at,
      :push_token,
      :push_platform,
      :alerts_enabled
    ])
    |> validate_required([:user_id])
    |> validate_inclusion(:push_platform, ["apns", "fcm", nil])
    |> put_token()
    |> put_timestamps()
    |> unique_constraint(:token)
  end

  @doc """
  Updates the last_used_at timestamp for a session.
  """
  def touch_changeset(mobile_session) do
    last_used_at = DateTime.truncate(DateTime.utc_now(), :second)
    change(mobile_session, last_used_at: last_used_at)
  end

  defp put_token(changeset) do
    if get_field(changeset, :token) do
      changeset
    else
      put_change(changeset, :token, generate_token())
    end
  end

  defp put_timestamps(changeset) do
    now = DateTime.truncate(DateTime.utc_now(), :second)
    default_expiration = now |> DateTime.add(90, :day) |> DateTime.truncate(:second)

    changeset
    |> put_change(:last_used_at, now)
    |> put_default_expiration(default_expiration)
  end

  defp put_default_expiration(changeset, default) do
    if get_field(changeset, :expires_at) do
      changeset
    else
      put_change(changeset, :expires_at, default)
    end
  end

  # Generate a secure random token (64 bytes = 512 bits)
  defp generate_token do
    64 |> :crypto.strong_rand_bytes() |> Base.url_encode64(padding: false)
  end
end
