defmodule BerrypodWeb.Admin.Providers.Form do
  use BerrypodWeb, :live_view

  alias Berrypod.{KeyValidation, Products}
  alias Berrypod.Products.ProviderConnection
  alias Berrypod.Providers.Provider

  @supported_types Enum.map(Provider.available(), & &1.type)

  @impl true
  def mount(params, _session, socket) do
    {:ok, apply_action(socket, socket.assigns.live_action, params)}
  end

  defp apply_action(socket, :new, params) do
    provider_type = validated_type(params["type"])
    provider = Provider.get(provider_type)

    socket
    |> assign(:page_title, "Connect to #{provider.name}")
    |> assign(:provider_type, provider_type)
    |> assign(:provider, provider)
    |> assign(:connection, %ProviderConnection{provider_type: provider_type})
    |> assign(:form, to_form(ProviderConnection.changeset(%ProviderConnection{}, %{})))
    |> assign(:pending_api_key, nil)
  end

  defp apply_action(socket, :edit, %{"id" => id}) do
    connection = Products.get_provider_connection!(id)
    provider = Provider.get(connection.provider_type)

    socket
    |> assign(:page_title, "#{provider.name} settings")
    |> assign(:provider_type, connection.provider_type)
    |> assign(:provider, provider)
    |> assign(:connection, connection)
    |> assign(:form, to_form(ProviderConnection.changeset(connection, %{})))
    |> assign(:pending_api_key, nil)
  end

  @impl true
  def handle_event("validate", %{"provider_connection" => params}, socket) do
    api_key = params["api_key"] || ""
    provider_type = socket.assigns.provider_type

    # Build base changeset
    changeset =
      socket.assigns.connection
      |> ProviderConnection.changeset(params)
      |> Map.put(:action, :validate)

    # Add key format validation error if key is present
    form =
      if api_key != "" do
        case KeyValidation.validate_provider_key(api_key, provider_type) do
          {:ok, _} ->
            to_form(changeset)

          {:error, message} ->
            changeset
            |> Ecto.Changeset.add_error(:api_key, message)
            |> to_form()
        end
      else
        to_form(changeset)
      end

    # Store api_key separately since changeset encrypts it immediately
    {:noreply, assign(socket, form: form, pending_api_key: api_key)}
  end

  @impl true
  def handle_event("save", %{"provider_connection" => params}, socket) do
    save_connection(socket, socket.assigns.live_action, params)
  end

  defp save_connection(socket, :new, params) do
    api_key = params["api_key"] || socket.assigns[:pending_api_key]
    provider_type = socket.assigns.provider_type

    case KeyValidation.validate_provider_key(api_key, provider_type) do
      {:error, message} ->
        form = form_with_api_key_error(socket, api_key, message)
        {:noreply, assign(socket, :form, form)}

      {:ok, api_key} ->
        case Products.connect_provider(api_key, provider_type) do
          {:ok, _connection} ->
            {:noreply,
             socket
             |> put_flash(:info, "Connected to #{socket.assigns.provider.name}!")
             |> push_navigate(to: ~p"/admin/settings")}

          {:error, _reason} ->
            form =
              form_with_api_key_error(
                socket,
                api_key,
                "Could not connect. Check your API key and try again"
              )

            {:noreply, assign(socket, :form, form)}
        end
    end
  end

  defp save_connection(socket, :edit, params) do
    case Products.update_provider_connection(socket.assigns.connection, params) do
      {:ok, _connection} ->
        {:noreply,
         socket
         |> put_flash(:info, "Settings saved")
         |> push_navigate(to: ~p"/admin/settings")}

      {:error, %Ecto.Changeset{} = changeset} ->
        {:noreply, assign(socket, form: to_form(changeset))}
    end
  end

  defp form_with_api_key_error(socket, api_key, message) do
    socket.assigns.connection
    |> ProviderConnection.changeset(%{"api_key" => api_key})
    |> Ecto.Changeset.add_error(:api_key, message)
    |> Map.put(:action, :validate)
    |> to_form()
  end

  defp validated_type(type) when type in @supported_types, do: type
  defp validated_type(_), do: "printify"
end
