defmodule Towerops.Accounts.UserRecoveryCode do
  @moduledoc """
  Schema for user recovery codes (backup codes for account recovery).

  Recovery codes are single-use backup codes that can be used to access
  an account if all TOTP devices are lost. Codes are stored as SHA-256 hashes.
  """
  use Ecto.Schema

  import Ecto.Changeset

  alias Towerops.Accounts.User

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id
  schema "user_recovery_codes" do
    field :code_hash, :string
    field :used_at, :utc_datetime
    field :created_at, :utc_datetime

    belongs_to :user, User

    timestamps(type: :utc_datetime, updated_at: false)
  end

  @doc """
  Generates a cryptographically secure 8-character recovery code.

  Format: XXXX-XXXX (uppercase letters and numbers, no ambiguous chars).
  Uses base32 alphabet without 0, O, I, 1 to avoid confusion.
  """
  def generate_code do
    # Use base32 alphabet without ambiguous characters (0, O, I, 1)
    alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"

    for_result =
      for _ <- 1..8 do
        Enum.random(String.graphemes(alphabet))
      end

    code = Enum.join(for_result)

    # Format: XXXX-XXXX
    String.slice(code, 0..3) <> "-" <> String.slice(code, 4..7)
  end

  @doc """
  Hashes a recovery code using SHA-256.

  ## Examples

      iex> hash_code("ABCD-EFGH")
      "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
  """
  def hash_code(code) when is_binary(code) do
    :sha256 |> :crypto.hash(code) |> Base.encode16(case: :lower)
  end

  @doc """
  Changeset for creating a recovery code.
  """
  def changeset(recovery_code, attrs) do
    recovery_code
    |> cast(attrs, [:code_hash, :user_id, :created_at])
    |> validate_required([:code_hash, :user_id, :created_at])
    |> unique_constraint(:code_hash)
    |> foreign_key_constraint(:user_id)
  end

  @doc """
  Changeset for marking a recovery code as used.
  """
  def use_changeset(recovery_code) do
    change(recovery_code, used_at: DateTime.utc_now(:second))
  end
end
