defmodule Raxol.Terminal.SessionManager.Helpers do
  @moduledoc """
  Helper functions for SessionManager operations.

  Provides utilities for session cleanup, timing, and other session management tasks.
  """

  @default_width Raxol.Core.Defaults.terminal_width()
  @default_height Raxol.Core.Defaults.terminal_height()
  @default_cleanup_interval_ms Raxol.Core.Defaults.health_check_interval_ms()

  @doc """
  Starts a cleanup timer for periodic session maintenance.

  ## Parameters
    - interval: Time interval in milliseconds for cleanup

  ## Returns
    Timer reference
  """
  @spec start_cleanup_timer(non_neg_integer()) :: reference()
  def start_cleanup_timer(interval)
      when is_integer(interval) and interval > 0 do
    Process.send_after(self(), :cleanup_sessions, interval)
  end

  def start_cleanup_timer(_interval) do
    # Default to 30 seconds if invalid interval provided
    Process.send_after(self(), :cleanup_sessions, @default_cleanup_interval_ms)
  end

  @doc """
  Cancels a cleanup timer.

  ## Parameters
    - timer_ref: Timer reference to cancel

  ## Returns
    - `:ok` if timer was canceled
    - `{:error, :not_found}` if timer doesn't exist
  """
  @spec cancel_cleanup_timer(reference() | nil) :: :ok | {:error, :not_found}
  def cancel_cleanup_timer(nil), do: :ok

  def cancel_cleanup_timer(timer_ref) when is_reference(timer_ref) do
    case Process.cancel_timer(timer_ref) do
      false -> {:error, :not_found}
      _ -> :ok
    end
  end

  @doc """
  Checks if a session has expired based on last activity.

  ## Parameters
    - last_activity: Timestamp of last activity
    - timeout: Timeout duration in milliseconds

  ## Returns
    Boolean indicating if session is expired
  """
  @spec session_expired?(integer(), non_neg_integer()) :: boolean()
  def session_expired?(last_activity, timeout)
      when is_integer(last_activity) and is_integer(timeout) do
    current_time = System.monotonic_time(:millisecond)
    current_time - last_activity > timeout
  end

  def session_expired?(_last_activity, _timeout), do: false

  @doc """
  Generates a unique session ID.

  ## Returns
    String session ID
  """
  @spec generate_session_id() :: String.t()
  def generate_session_id do
    :crypto.strong_rand_bytes(16)
    |> Base.encode64(padding: false)
    |> String.replace(["+", "/"], fn
      "+" -> "-"
      "/" -> "_"
    end)
  end

  @doc """
  Validates session configuration.

  ## Parameters
    - config: Session configuration map

  ## Returns
    - `{:ok, config}` if valid
    - `{:error, reason}` if invalid
  """
  @spec validate_session_config(map()) :: {:ok, map()} | {:error, atom()}
  def validate_session_config(config) when is_map(config) do
    with :ok <- validate_dimensions(config),
         :ok <- validate_timeout(config) do
      {:ok, config}
    end
  end

  def validate_session_config(_config), do: {:error, :invalid_config}

  defp validate_dimensions(%{width: w, height: h})
       when is_integer(w) and w > 0 and is_integer(h) and h > 0,
       do: :ok

  # Dimensions are optional - matches any map
  defp validate_dimensions(%{}), do: :ok

  defp validate_timeout(%{timeout: t}) when is_integer(t) and t > 0, do: :ok
  # Timeout is optional - matches any map
  defp validate_timeout(%{}), do: :ok

  @doc """
  Merges session options with defaults.

  ## Parameters
    - opts: User-provided options
    - defaults: Default options

  ## Returns
    Merged options map
  """
  @spec merge_session_options(Keyword.t() | map(), map()) :: map()
  def merge_session_options(opts, defaults) when is_list(opts) do
    opts
    |> Enum.into(%{})
    |> merge_session_options(defaults)
  end

  def merge_session_options(opts, defaults)
      when is_map(opts) and is_map(defaults) do
    Map.merge(defaults, opts)
  end

  def merge_session_options(_opts, defaults), do: defaults

  @doc """
  Formats session info for display.

  ## Parameters
    - session: Session data map

  ## Returns
    Formatted string
  """
  @spec format_session_info(map()) :: String.t()
  def format_session_info(%{id: id, created_at: created, active: active} = session) do
    status = if active, do: "active", else: "inactive"
    dimensions = get_dimensions_string(session)

    "Session #{id} (#{status}) - Created: #{format_timestamp(created)}#{dimensions}"
  end

  def format_session_info(_), do: "Invalid session"

  defp get_dimensions_string(%{width: w, height: h}), do: " - Size: #{w}x#{h}"
  defp get_dimensions_string(_), do: ""

  defp format_timestamp(timestamp) when is_integer(timestamp) do
    timestamp
    |> DateTime.from_unix!(:millisecond)
    |> DateTime.to_string()
  end

  defp format_timestamp(_), do: "unknown"

  @doc """
  Creates a Client struct from an id, session_id, and config map.
  """
  def create_client(client_id, session_id, config) do
    alias Raxol.Terminal.SessionManager.Client

    %Client{
      id: client_id,
      session_id: session_id,
      connection_type: Map.get(config, :connection_type, :local),
      connected_at: System.monotonic_time(:millisecond),
      last_activity: System.monotonic_time(:millisecond),
      terminal_size: Map.get(config, :terminal_size, {@default_width, @default_height}),
      capabilities: Map.get(config, :capabilities, [:resize, :color, :mouse]),
      metadata: Map.get(config, :metadata, %{})
    }
  end

  @doc """
  Generates a unique session ID based on name and timestamp.
  """
  def generate_session_id(name) do
    timestamp = System.unique_integer([:positive, :monotonic])

    Base.encode16(:crypto.hash(:sha256, "#{name}-#{timestamp}"))
    |> String.slice(0, 16)
  end

  @doc """
  Generates a unique client ID.
  """
  def generate_client_id do
    "client_" <> Base.encode16(:crypto.strong_rand_bytes(4))
  end

  @doc """
  Sends input to a terminal process if it is alive.
  """
  def send_to_terminal(terminal_pid, input) do
    if Process.alive?(terminal_pid) do
      GenServer.call(terminal_pid, {:send_input, input})
    else
      {:error, :terminal_dead}
    end
  end

  @doc """
  Initializes the network server for session sharing.
  """
  def init_network_server(false, _port), do: nil

  def init_network_server(true, port) do
    alias Raxol.Core.Runtime.Log
    Log.info("Session sharing server started on port #{port}")
    %{port: port, enabled: true}
  end
end
