defmodule Aprsme.Cache do
  @moduledoc """
  Cache abstraction layer using ETS (Erlang Term Storage).
  Provides a unified API for in-memory caching with optional TTL support.

  Entries are stored as `{key, value, expires_at}` tuples where `expires_at`
  is a monotonic time in milliseconds, or `:infinity` for no expiration.
  Expired entries are lazily evicted on read.
  """

  @doc """
  Get a value from cache. Returns `{:ok, nil}` for expired entries.
  """
  def get(cache_name, key) do
    case :ets.lookup(cache_name, key) do
      [{^key, value, :infinity}] ->
        {:ok, value}

      [{^key, value, expires_at}] ->
        if System.monotonic_time(:millisecond) < expires_at do
          {:ok, value}
        else
          :ets.delete(cache_name, key)
          {:ok, nil}
        end

      [{^key, value}] ->
        {:ok, value}

      [] ->
        {:ok, nil}
    end
  rescue
    ArgumentError -> {:error, :no_cache}
  end

  @doc """
  Put a value in cache with optional TTL.

  ## Options
    * `:ttl` - Time to live in milliseconds. Use `Cache.to_timeout/1` for convenience.
  """
  def put(cache_name, key, value, opts \\ []) do
    expires_at =
      case Keyword.get(opts, :ttl) do
        nil -> :infinity
        ttl when is_integer(ttl) and ttl > 0 -> System.monotonic_time(:millisecond) + ttl
        _ -> :infinity
      end

    :ets.insert(cache_name, {key, value, expires_at})
    {:ok, true}
  rescue
    ArgumentError -> {:error, :no_cache}
  end

  @doc """
  Delete a key from cache
  """
  def del(cache_name, key) do
    :ets.delete(cache_name, key)
    {:ok, true}
  rescue
    ArgumentError -> {:error, :no_cache}
  end

  @doc """
  Clear all keys from cache
  """
  def clear(cache_name) do
    :ets.delete_all_objects(cache_name)
    {:ok, true}
  rescue
    ArgumentError -> {:error, :no_cache}
  end

  @doc """
  Get cache statistics (simplified for ETS)
  """
  def stats(cache_name) do
    case :ets.info(cache_name) do
      :undefined -> {:error, :no_cache}
      info when is_list(info) -> {:ok, %{size: Keyword.get(info, :size, 0)}}
    end
  end

  @doc """
  Check if key exists and is not expired.
  """
  def exists?(cache_name, key) do
    case get(cache_name, key) do
      {:ok, nil} -> false
      {:ok, _} -> true
      _ -> false
    end
  end

  @doc """
  Get TTL for a key. Returns remaining milliseconds or nil if no TTL.
  """
  def ttl(cache_name, key) do
    case :ets.lookup(cache_name, key) do
      [{^key, _value, :infinity}] -> {:ok, nil}
      [{^key, _value, expires_at}] -> {:ok, max(0, expires_at - System.monotonic_time(:millisecond))}
      [{^key, _value}] -> {:ok, nil}
      [] -> {:ok, nil}
    end
  rescue
    ArgumentError -> {:ok, nil}
  end

  @doc """
  Convert timeout keyword list to milliseconds.
  """
  def to_timeout(opts) do
    Enum.reduce(opts, 0, fn
      {:second, n}, acc -> acc + n * 1000
      {:seconds, n}, acc -> acc + n * 1000
      {:minute, n}, acc -> acc + n * 60 * 1000
      {:minutes, n}, acc -> acc + n * 60 * 1000
      {:hour, n}, acc -> acc + n * 60 * 60 * 1000
      {:hours, n}, acc -> acc + n * 60 * 60 * 1000
      {:day, n}, acc -> acc + n * 24 * 60 * 60 * 1000
      {:days, n}, acc -> acc + n * 24 * 60 * 60 * 1000
      {:millisecond, n}, acc -> acc + n
      {:milliseconds, n}, acc -> acc + n
    end)
  end
end
