defmodule BullxTelegram.Config.IMGatewaySources do
  @moduledoc false

  use Skogsra.Type

  import BullX.Config.MapType,
    only: [required_string: 2, optional_boolean: 3, optional_map: 3]

  @deprecated_stream_config_keys ["stream_update_interval_ms", "stream_chunk_soft_limit"]

  @impl Skogsra.Type
  def cast(value) when is_binary(value) do
    with {:ok, decoded} <- Jason.decode(value), do: cast(decoded), else: (_error -> :error)
  end

  def cast(value) when is_list(value) do
    value
    |> Enum.map(&normalize_source/1)
    |> collect_sources()
  end

  def cast(_value), do: :error

  defp normalize_source(%{} = source) do
    with {:ok, source} <- stringify_keys(source),
         {:ok, id} <- required_string(source, "id"),
         {:ok, bot_token} <- required_string(source, "bot_token"),
         {:ok, enabled?} <- optional_boolean(source, "enabled", true),
         {:ok, attention} <- optional_map(source, "attention", %{}),
         {:ok, commands} <- optional_map(source, "commands", %{}) do
      {:ok,
       source
       |> Map.drop(@deprecated_stream_config_keys)
       |> Map.put("id", id)
       |> Map.put("bot_token", bot_token)
       |> Map.put("enabled", enabled?)
       |> Map.put("attention", attention)
       |> Map.put("commands", commands)}
    end
  end

  defp normalize_source(_source), do: :error

  defp collect_sources(sources) do
    case Enum.all?(sources, &match?({:ok, _source}, &1)) do
      true -> {:ok, Enum.map(sources, fn {:ok, source} -> source end)}
      false -> :error
    end
  end

  defp stringify_keys(%{} = map) do
    Enum.reduce_while(map, {:ok, %{}}, fn
      {key, value}, {:ok, acc} when is_atom(key) ->
        {:cont, {:ok, Map.put(acc, Atom.to_string(key), stringify_value(value))}}

      {key, value}, {:ok, acc} when is_binary(key) ->
        {:cont, {:ok, Map.put(acc, key, stringify_value(value))}}

      _entry, _acc ->
        {:halt, :error}
    end)
  end

  defp stringify_value(%{} = map), do: elem(stringify_keys(map), 1)
  defp stringify_value(values) when is_list(values), do: Enum.map(values, &stringify_value/1)
  defp stringify_value(value), do: value
end

defmodule BullxTelegram.Config do
  @moduledoc """
  Runtime configuration declarations owned by the Telegram plugin.

  Each configured Telegram source is one BullX channel instance backed by one
  bot token. The source list is encrypted because it includes bot tokens.
  """

  use BullX.Config

  @envdoc false
  bullx_env(:im_gateway_sources,
    key: [:plugins, :bullx_telegram, :im_gateway_sources],
    type: BullxTelegram.Config.IMGatewaySources,
    default: [],
    secret: true
  )
end
