defmodule ToweropsWeb.UserSettingsLive do
  @moduledoc """
  LiveView for managing user account settings.

  Allows users to update email, password, and mobile sessions.
  """
  use ToweropsWeb, :live_view

  alias Towerops.Accounts
  alias Towerops.Accounts.HIBP
  alias Towerops.Admin.AuditLogger
  alias ToweropsWeb.UserSettingsLive.ApiTokenManager
  alias ToweropsWeb.UserSettingsLive.Helpers
  alias ToweropsWeb.UserSettingsLive.SessionManager
  alias ToweropsWeb.UserSettingsLive.TotpManager

  @impl true
  def mount(_params, session, socket) do
    current_token = session["user_token"]

    socket =
      socket
      |> assign(:page_title, "Account Settings")
      |> assign(:current_token, current_token)
      |> assign_changesets()
      |> assign_profile_form()
      |> assign_mobile_sessions()
      |> assign_api_tokens()
      |> assign_organizations()
      |> assign_default_organization()
      |> assign_browser_sessions()
      |> assign_login_history()
      |> assign_security_alerts()
      |> assign_totp_devices()
      |> assign_recovery_codes_count()
      |> assign(:show_add_token_modal, false)
      |> assign(:show_token_modal, false)
      |> assign(:created_token, nil)
      |> assign(:show_revoke_all_modal, false)
      |> assign(:login_history_page, 1)
      |> assign(:show_add_device_modal, false)
      |> assign(:show_device_qr_modal, false)
      |> assign(:show_recovery_codes_modal, false)
      |> assign(:new_device_id, nil)
      |> assign(:new_device_secret, nil)
      |> assign(:new_device_qr_code, nil)
      |> assign(:generated_recovery_codes, nil)
      |> assign(:password_breach_count, nil)

    {:ok, socket}
  end

  @impl true
  def handle_params(params, _url, socket) do
    # Default to "personal" tab if no tab param is provided
    tab = Map.get(params, "tab", "personal")

    # Get page from params, default to 1
    page = params |> Map.get("page", "1") |> String.to_integer()

    # Read modal state from URL
    modal = params["modal"]

    socket =
      socket
      |> assign(:active_tab, tab)
      |> assign(:login_history_page, page)
      |> assign(:show_add_token_modal, modal == "add_token")
      |> assign(:show_token_modal, modal == "token_created")
      |> assign(:show_revoke_all_modal, modal == "revoke_all")
      |> assign(:show_add_device_modal, modal == "add_device")
      |> assign(:show_device_qr_modal, modal == "device_qr")
      |> assign(:show_recovery_codes_modal, modal == "recovery_codes")
      |> assign_login_history()

    {:noreply, socket}
  end

  @impl true
  def handle_event("show_add_token_modal", _params, socket) do
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{"modal" => "add_token"}))}
  end

  @impl true
  def handle_event("cancel_add_token", _params, socket) do
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{}))}
  end

  @impl true
  def handle_event("update_profile", %{"user" => user_params}, socket) do
    user = socket.assigns.current_scope.user

    case Accounts.update_user_profile(user, user_params) do
      {:ok, updated_user} ->
        # Log profile update
        fields_changed = Map.keys(user_params)
        AuditLogger.log_user_profile_updated(nil, user.id, fields_changed)

        # Update current_scope with new user data (including timezone and time_format)
        updated_scope = %{
          socket.assigns.current_scope
          | user: updated_user,
            timezone: updated_user.timezone || "UTC",
            time_format: updated_user.time_format || "24h"
        }

        socket =
          socket
          |> assign(:current_scope, updated_scope)
          |> put_flash(:info, "Profile updated successfully.")
          |> assign_profile_form()

        {:noreply, socket}

      {:error, changeset} ->
        {:noreply, assign(socket, :profile_form, to_form(changeset))}
    end
  end

  @impl true
  def handle_event("update_email", %{"user" => user_params}, socket) do
    user = socket.assigns.current_scope.user

    case Accounts.change_user_email(user, user_params) do
      %{valid?: true} = changeset ->
        _ =
          Accounts.deliver_user_update_email_instructions(
            Ecto.Changeset.apply_action!(changeset, :insert),
            user.email,
            &url(~p"/users/settings/confirm-email/#{&1}")
          )

        socket =
          socket
          |> put_flash(:info, "A link to confirm your email change has been sent to the new address.")
          |> assign_changesets()

        {:noreply, socket}

      changeset ->
        {:noreply, assign(socket, :email_form, to_form(%{changeset | action: :insert}))}
    end
  end

  @impl true
  def handle_event("check_password_breach", %{"value" => password}, socket)
      when is_binary(password) and byte_size(password) > 0 do
    case HIBP.check_password(password) do
      {:ok, count} when is_integer(count) ->
        {:noreply, assign(socket, :password_breach_count, count)}

      {:ok, :unknown} ->
        {:noreply, assign(socket, :password_breach_count, nil)}
    end
  end

  def handle_event("check_password_breach", _params, socket) do
    {:noreply, socket}
  end

  @impl true
  def handle_event("update_password", %{"user" => user_params}, socket) do
    user = socket.assigns.current_scope.user

    case Accounts.update_user_password(user, user_params) do
      {:ok, {_updated_user, _}} ->
        # Redirect to re-authenticate with new password
        socket =
          socket
          |> put_flash(:info, "Password updated successfully. Please log in with your new password.")
          |> redirect(to: ~p"/users/log-in")

        {:noreply, socket}

      {:error, changeset} ->
        {:noreply, assign(socket, :password_form, to_form(changeset))}
    end
  end

  @impl true
  # Mobile session management - delegated to SessionManager
  def handle_event("revoke_mobile_device", %{"session-id" => session_id}, socket) do
    {:noreply, SessionManager.revoke_mobile_device(socket, session_id)}
  end

  @impl true
  def handle_event("toggle_device_alerts", %{"session-id" => session_id, "enabled" => enabled}, socket) do
    {:noreply, SessionManager.toggle_device_alerts(socket, session_id, enabled)}
  end

  # API token management - delegated to ApiTokenManager
  @impl true
  def handle_event("create_api_token", params, socket) do
    socket = ApiTokenManager.create_api_token(socket, params)
    # After creating token, show the token_created modal
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{"modal" => "token_created"}))}
  end

  @impl true
  def handle_event("close_token_modal", _params, socket) do
    socket = ApiTokenManager.close_token_modal(socket)
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{}))}
  end

  @impl true
  def handle_event("delete_api_token", %{"token-id" => token_id}, socket) do
    {:noreply, ApiTokenManager.delete_api_token(socket, token_id)}
  end

  # Browser session management - delegated to SessionManager
  @impl true
  def handle_event("revoke_session", %{"session-id" => session_id}, socket) do
    {:noreply, SessionManager.revoke_session(socket, session_id)}
  end

  @impl true
  def handle_event("show_revoke_all_modal", _params, socket) do
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{"modal" => "revoke_all"}))}
  end

  @impl true
  def handle_event("cancel_revoke_all", _params, socket) do
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{}))}
  end

  @impl true
  def handle_event("confirm_revoke_all", _params, socket) do
    socket = SessionManager.confirm_revoke_all(socket)
    # After revoking all sessions, close modal
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{}))}
  end

  # Security tab - TOTP Device Management

  @impl true
  # TOTP device management - delegated to TotpManager
  def handle_event("show_add_device_modal", _params, socket) do
    socket = TotpManager.show_add_device_modal(socket)
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{"modal" => "add_device"}))}
  end

  @impl true
  def handle_event("cancel_add_device", _params, socket) do
    socket = TotpManager.cancel_add_device(socket)
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{}))}
  end

  @impl true
  def handle_event("create_device", %{"name" => name}, socket) do
    socket = TotpManager.create_device(socket, name)
    # After creating device, show the QR code modal
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{"modal" => "device_qr"}))}
  end

  @impl true
  def handle_event("verify_new_device", %{"code" => code}, socket) do
    socket = TotpManager.verify_new_device(socket, code)
    # After verifying device, close modal
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{}))}
  end

  @impl true
  def handle_event("close_device_qr_modal", _params, socket) do
    socket = TotpManager.close_device_qr_modal(socket)
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{}))}
  end

  @impl true
  def handle_event("delete_device", %{"device-id" => device_id}, socket) do
    {:noreply, TotpManager.delete_device(socket, device_id)}
  end

  # Recovery codes - delegated to TotpManager
  @impl true
  def handle_event("regenerate_recovery_codes", _params, socket) do
    socket = TotpManager.regenerate_recovery_codes(socket)
    # After regenerating codes, show the recovery codes modal
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{"modal" => "recovery_codes"}))}
  end

  @impl true
  def handle_event("close_recovery_codes_modal", _params, socket) do
    socket = TotpManager.close_recovery_codes_modal(socket)
    {:noreply, push_patch(socket, to: build_settings_path(socket, %{}))}
  end

  defp get_current_token_id(%{current_token: token}) when is_binary(token) do
    Accounts.get_user_token_id_by_value(token)
  end

  defp get_current_token_id(_), do: nil

  defp assign_profile_form(socket) do
    user = socket.assigns.current_scope.user
    assign(socket, :profile_form, user |> Accounts.change_user_profile() |> to_form())
  end

  defp assign_changesets(socket) do
    user = socket.assigns.current_scope.user

    socket
    |> assign(:email_form, user |> Accounts.change_user_email() |> to_form())
    |> assign(:password_form, user |> Accounts.change_user_password() |> to_form())
  end

  # Assignment helpers delegated to helper modules
  defdelegate assign_mobile_sessions(socket), to: SessionManager
  defdelegate assign_api_tokens(socket), to: ApiTokenManager
  defdelegate assign_browser_sessions(socket), to: SessionManager
  defdelegate assign_totp_devices(socket), to: TotpManager
  defdelegate assign_recovery_codes_count(socket), to: TotpManager

  defp assign_organizations(socket) do
    user = socket.assigns.current_scope.user
    assign(socket, :organizations, Towerops.Organizations.list_user_organizations(user.id))
  end

  defp assign_default_organization(socket) do
    user = socket.assigns.current_scope.user
    organizations = socket.assigns.organizations

    # Get user's preferred default org, or fall back to first org
    default_org =
      if user.default_organization_id do
        Enum.find(organizations, &(&1.id == user.default_organization_id)) || List.first(organizations)
      else
        List.first(organizations)
      end

    assign(socket, :default_organization, default_org)
  end

  defp assign_login_history(socket) do
    user = socket.assigns.current_scope.user
    page = socket.assigns[:login_history_page] || 1
    per_page = 5
    offset = (page - 1) * per_page

    # Get total count and history for current page
    total_count = Accounts.count_user_login_attempts(user.id)
    history = Accounts.list_user_login_history(user.id, limit: per_page, offset: offset)

    total_pages = ceil(total_count / per_page)
    page = max(1, min(page, max(1, total_pages)))

    socket
    |> assign(:login_history, history)
    |> assign(:login_history_pagination, %{
      page: page,
      per_page: per_page,
      total_count: total_count,
      total_pages: total_pages
    })
  end

  defp assign_security_alerts(socket) do
    user = socket.assigns.current_scope.user
    since = DateTime.add(DateTime.utc_now(), -24 * 60 * 60, :second)
    failed_count = Accounts.count_user_login_attempts(user.id, success: false, since: since)

    socket
    |> assign(:failed_login_count, failed_count)
    |> assign(:show_security_alert, failed_count >= 3)
  end

  # URL building helper
  defp build_settings_path(socket, extra_params) do
    # Preserve current tab and page
    base_params = %{
      "tab" => socket.assigns.active_tab,
      "page" => to_string(socket.assigns.login_history_page)
    }

    # Merge with extra params, removing nil values
    params =
      base_params
      |> Map.merge(extra_params)
      |> Enum.reject(fn {_k, v} -> is_nil(v) end)
      |> Map.new()

    # Remove page param if it's 1 (default)
    params = if params["page"] == "1", do: Map.delete(params, "page"), else: params

    # Build path with query string
    if map_size(params) > 0 do
      ~p"/users/settings?#{params}"
    else
      ~p"/users/settings"
    end
  end

  # Sessions tab helper functions

  # Formatting helpers delegated to ToweropsWeb.UserSettingsLive.Helpers module
  defdelegate current_session?(session, current_token_id), to: Helpers
  defdelegate format_timestamp_in_timezone(datetime, timezone, time_format \\ "24h"), to: Helpers
  defdelegate format_browser_info(session), to: Helpers
  defdelegate format_location(record), to: Helpers
  defdelegate format_relative_time(datetime), to: Helpers
  defdelegate login_method_info(method), to: Helpers
  defdelegate pagination_range(current_page, total_pages), to: Helpers
end
