defmodule ToweropsWeb.UserSettingsLive.TotpManager do
  @moduledoc """
  Handles TOTP (Time-based One-Time Password) device and recovery code management.
  """
  use Gettext, backend: ToweropsWeb.Gettext

  import ToweropsWeb.GettextHelpers

  alias Towerops.Accounts
  alias Towerops.Accounts.UserTotpDevice

  @doc """
  Shows the modal for adding a new TOTP device.
  """
  def show_add_device_modal(socket) do
    Phoenix.Component.assign(socket, :show_add_device_modal, true)
  end

  @doc """
  Cancels adding a new TOTP device.
  """
  def cancel_add_device(socket) do
    Phoenix.Component.assign(socket, :show_add_device_modal, false)
  end

  @doc """
  Creates a new TOTP device and displays the QR code.
  """
  def create_device(socket, name) do
    user = socket.assigns.current_scope.user

    case Accounts.create_totp_device(user.id, name) do
      {:ok, device, secret} ->
        qr_code = Accounts.generate_totp_qr_code(user, secret)

        socket
        |> Phoenix.Component.assign(:show_add_device_modal, false)
        |> Phoenix.Component.assign(:show_device_qr_modal, true)
        |> Phoenix.Component.assign(:new_device_id, device.id)
        |> Phoenix.Component.assign(:new_device_secret, secret)
        |> Phoenix.Component.assign(:new_device_qr_code, qr_code)

      {:error, _changeset} ->
        Phoenix.LiveView.put_flash(socket, :error, t_auth("Failed to create device."))
    end
  end

  @doc """
  Verifies a new TOTP device with a code from the authenticator app.
  """
  def verify_new_device(socket, code) do
    secret = socket.assigns.new_device_secret
    device_id = socket.assigns.new_device_id

    if Accounts.verify_totp(secret, code) do
      # Device already created, just mark as verified by touching it
      device = Towerops.Repo.get!(UserTotpDevice, device_id)

      device
      |> UserTotpDevice.touch_changeset()
      |> Towerops.Repo.update!()

      socket
      |> Phoenix.LiveView.put_flash(:info, t_auth("Device added successfully!"))
      |> Phoenix.Component.assign(:show_device_qr_modal, false)
      |> Phoenix.Component.assign(:new_device_id, nil)
      |> Phoenix.Component.assign(:new_device_secret, nil)
      |> Phoenix.Component.assign(:new_device_qr_code, nil)
      |> assign_totp_devices()
    else
      Phoenix.LiveView.put_flash(socket, :error, t_auth("Invalid code. Please try again."))
    end
  end

  @doc """
  Closes the QR code modal and deletes the unverified device.
  """
  def close_device_qr_modal(socket) do
    # Delete the unverified device if user cancels
    if device_id = socket.assigns.new_device_id do
      user = socket.assigns.current_scope.user
      Accounts.delete_totp_device(device_id, user.id)
    end

    socket
    |> Phoenix.Component.assign(:show_device_qr_modal, false)
    |> Phoenix.Component.assign(:new_device_id, nil)
    |> Phoenix.Component.assign(:new_device_secret, nil)
    |> Phoenix.Component.assign(:new_device_qr_code, nil)
    |> assign_totp_devices()
  end

  @doc """
  Deletes a TOTP device.
  """
  def delete_device(socket, device_id) do
    user = socket.assigns.current_scope.user

    case Accounts.delete_totp_device(device_id, user.id) do
      {:ok, _} ->
        socket
        |> Phoenix.LiveView.put_flash(:info, t_auth("Device removed successfully."))
        |> assign_totp_devices()

      {:error, :last_device} ->
        Phoenix.LiveView.put_flash(
          socket,
          :error,
          t_auth("Cannot remove last device. You must have at least one.")
        )

      {:error, _} ->
        Phoenix.LiveView.put_flash(socket, :error, t_auth("Failed to remove device."))
    end
  end

  @doc """
  Regenerates recovery codes for the user.
  """
  def regenerate_recovery_codes(socket) do
    user = socket.assigns.current_scope.user

    case Accounts.generate_recovery_codes(user.id) do
      {:ok, codes} ->
        socket
        |> Phoenix.Component.assign(:show_recovery_codes_modal, true)
        |> Phoenix.Component.assign(:generated_recovery_codes, codes)
        |> assign_recovery_codes_count()

      {:error, _} ->
        Phoenix.LiveView.put_flash(socket, :error, t_auth("Failed to generate recovery codes."))
    end
  end

  @doc """
  Closes the recovery codes modal.
  """
  def close_recovery_codes_modal(socket) do
    socket
    |> Phoenix.Component.assign(:show_recovery_codes_modal, false)
    |> Phoenix.Component.assign(:generated_recovery_codes, nil)
  end

  @doc """
  Assigns TOTP devices to socket.
  """
  def assign_totp_devices(socket) do
    user = socket.assigns.current_scope.user
    devices = Accounts.list_user_totp_devices(user.id)
    Phoenix.Component.assign(socket, :totp_devices, devices)
  end

  @doc """
  Assigns recovery codes count to socket.
  """
  def assign_recovery_codes_count(socket) do
    user = socket.assigns.current_scope.user
    count = Accounts.count_unused_recovery_codes(user.id)
    Phoenix.Component.assign(socket, :unused_recovery_codes_count, count)
  end
end
