defmodule SimpleshopTheme.Settings do
  @moduledoc """
  The Settings context for managing site-wide configuration.
  """

  import Ecto.Query, warn: false
  alias SimpleshopTheme.Repo
  alias SimpleshopTheme.Settings.{Setting, ThemeSettings}

  @doc """
  Gets a setting by key with an optional default value.

  ## Examples

      iex> get_setting("site_name", "My Shop")
      "My Shop"

  """
  def get_setting(key, default \\ nil) do
    case Repo.get_by(Setting, key: key) do
      nil -> default
      setting -> decode_value(setting)
    end
  end

  @doc """
  Sets a setting value by key.

  ## Examples

      iex> put_setting("site_name", "My Awesome Shop")
      {:ok, %Setting{}}

  """
  def put_setting(key, value, value_type \\ "string") do
    encoded_value = encode_value(value, value_type)

    %Setting{key: key}
    |> Setting.changeset(%{key: key, value: encoded_value, value_type: value_type})
    |> Repo.insert(
      on_conflict: {:replace, [:value, :value_type, :updated_at]},
      conflict_target: :key
    )
  end

  @doc """
  Gets the theme settings as a ThemeSettings struct.

  ## Examples

      iex> get_theme_settings()
      %ThemeSettings{mood: "neutral", typography: "clean", ...}

  """
  def get_theme_settings do
    case get_setting("theme_settings") do
      nil ->
        # Return defaults
        %ThemeSettings{}

      settings_map when is_map(settings_map) ->
        settings_map
        |> atomize_keys()
        |> then(&struct(ThemeSettings, &1))
    end
  end

  @doc """
  Updates the theme settings.

  ## Examples

      iex> update_theme_settings(%{mood: "dark", typography: "modern"})
      {:ok, %ThemeSettings{}}

  """
  def update_theme_settings(attrs) when is_map(attrs) do
    current = get_theme_settings()

    changeset = ThemeSettings.changeset(current, attrs)

    if changeset.valid? do
      settings = Ecto.Changeset.apply_changes(changeset)
      json = Jason.encode!(settings)
      put_setting("theme_settings", json, "json")

      # Invalidate and rewarm CSS cache
      alias SimpleshopTheme.Theme.{CSSCache, CSSGenerator}
      CSSCache.invalidate()
      css = CSSGenerator.generate(settings)
      CSSCache.put(css)

      {:ok, settings}
    else
      {:error, changeset}
    end
  end

  @doc """
  Applies a preset to theme settings.

  ## Examples

      iex> apply_preset(:gallery)
      {:ok, %ThemeSettings{}}

  """
  def apply_preset(preset_name) when is_atom(preset_name) do
    preset = SimpleshopTheme.Theme.Presets.get(preset_name)

    if preset do
      update_theme_settings(preset)
    else
      {:error, :preset_not_found}
    end
  end

  # Private helpers

  defp decode_value(%Setting{value: value, value_type: "json"}), do: Jason.decode!(value)
  defp decode_value(%Setting{value: value, value_type: "integer"}), do: String.to_integer(value)

  defp decode_value(%Setting{value: value, value_type: "boolean"}),
    do: value == "true"

  defp decode_value(%Setting{value: value, value_type: "string"}), do: value

  defp encode_value(value, "json") when is_binary(value), do: value
  defp encode_value(value, "json"), do: Jason.encode!(value)
  defp encode_value(value, "integer") when is_integer(value), do: Integer.to_string(value)
  defp encode_value(value, "boolean") when is_boolean(value), do: Atom.to_string(value)
  defp encode_value(value, "string") when is_binary(value), do: value

  defp atomize_keys(map) when is_map(map) do
    Map.new(map, fn
      {key, value} when is_binary(key) -> {String.to_atom(key), value}
      {key, value} -> {key, value}
    end)
  end
end
