defmodule Riverside.IO.Timestamp.Sandbox do
  @behaviour Riverside.IO.Timestamp.Behaviour

  require Logger

  use GenServer
  alias Riverside.IO.Timestamp.Real

  @type mode :: :fixture | :real

  defstruct stack: [],
            mode: :fixture

  def mode(mode) do
    GenServer.call(__MODULE__, {:set_mode, mode})
  end

  @impl Riverside.IO.Timestamp.Behaviour
  def seconds() do
    {:ok, seconds} = GenServer.call(__MODULE__, :get_seconds)
    Logger.debug("<Riverrise.Timestamp.Sandbox> seconds/0 returns: #{seconds}")
    seconds
  end

  @impl Riverside.IO.Timestamp.Behaviour
  def milli_seconds() do
    {:ok, milli_seconds} = GenServer.call(__MODULE__, :get_milli_seconds)
    Logger.debug("<Riverrise.Timestamp.Sandbox> milli_seconds/0 returns: #{milli_seconds}")
    milli_seconds
  end

  def set_seconds(list) when is_list(list) do
    Logger.debug("<Riverrise.Timestamp.Sandbox> refreshes to #{inspect(list)}")
    GenServer.call(__MODULE__, {:set_seconds_list, list})
  end

  def set_seconds(seconds) do
    set_seconds([seconds])
  end

  def set_milli_seconds(list) when is_list(list) do
    Logger.debug("<Riverrise.Timestamp.Sandbox> refreshes to #{inspect(list)}")
    GenServer.call(__MODULE__, {:set_milli_seconds_list, list})
  end

  def set_milli_seconds(milli_seconds) do
    set_milli_seconds([milli_seconds])
  end

  def start_link(opts \\ []) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end

  @impl GenServer
  def init(opts) when is_list(opts) do
    {:ok, %__MODULE__{stack: opts, mode: :fixture}}
  end

  @impl GenServer
  def handle_call({:set_mode, mode}, _from, state) do
    {:reply, :ok, %{state | mode: mode}}
  end

  def handle_call(:get_seconds, _from, %{mode: :real} = state) do
    {:reply, {:ok, Real.seconds()}, state}
  end

  def handle_call(:get_seconds, _from, %{stack: stack} = state) do
    {ms, stack2} = shift_stack(stack)
    {:reply, {:ok, div(ms, 1_000)}, %{state | stack: stack2}}
  end

  def handle_call(:get_milli_seconds, _from, %{mode: :real} = state) do
    {:reply, {:ok, Real.milli_seconds()}, state}
  end

  def handle_call(:get_milli_seconds, _from, %{stack: stack} = state) do
    {ms, stack2} = shift_stack(stack)
    {:reply, {:ok, ms}, %{state | stack: stack2}}
  end

  def handle_call({:set_seconds_list, list}, _from, state) do
    stack = list |> Enum.map(&:erlang.*(&1, 1_000))
    {:reply, :ok, %{state | stack: stack}}
  end

  def handle_call({:set_milli_seconds_list, stack}, _from, state) do
    {:reply, :ok, %{state | stack: stack}}
  end

  @impl GenServer
  def terminate(_reason, _state) do
    :ok
  end

  defp shift_stack([]) do
    raise "<Riverrise.Timestamp.Sandbox> No more dummy timestamp data, set enough amount of them"
  end

  defp shift_stack([first | rest]) do
    {first, rest}
  end
end
