defmodule Berrypod.Providers.Provider do
  @moduledoc """
  Behaviour and registry for POD provider integrations.

  Each provider (Printify, Printful, etc.) implements this behaviour
  to provide a consistent interface for:

  - Testing connections
  - Fetching products
  - Submitting orders
  - Tracking order status

  The `@providers` list is the single source of truth for available providers.
  Both the module dispatch (`for_type/1`) and UI metadata (`all/0`, `get/1`)
  are derived from it.

  ## Data Normalization

  Providers return normalized data structures:

  - Products are maps with keys: `title`, `description`, `provider_product_id`,
    `images`, `variants`, `category`, `provider_data`
  - Variants are maps with keys: `provider_variant_id`, `title`, `sku`, `price`,
    `cost`, `options`, `is_enabled`, `is_available`
  - Images are maps with keys: `src`, `position`, `alt`
  """

  alias Berrypod.Products.ProviderConnection

  # Single source of truth for all providers — module dispatch and UI metadata.
  # Add new providers here. Set module: nil for coming-soon entries.
  @providers [
    %{
      type: "printify",
      module: Berrypod.Providers.Printify,
      name: "Printify",
      tagline: "Largest catalog — 800+ products from 90+ print providers",
      features: ["Biggest product selection", "Multi-provider routing", "Competitive pricing"],
      setup_hint: "Get your API token from Printify → Account → Connections",
      setup_url: "https://printify.com/app/account/connections",
      login_url: "https://printify.com/app/auth/login",
      signup_url: "https://printify.com/app/auth/register",
      setup_steps: [
        ~s[Click <strong>Account</strong> (top right)],
        ~s[Select <strong>Connections</strong> from the dropdown],
        ~s[Find <strong>API tokens</strong> and click <strong>Generate</strong>],
        ~s[Enter a name (e.g. "My Shop"), keep <strong>all scopes</strong> selected, and click <strong>Generate token</strong>],
        ~s[Click <strong>Copy to clipboard</strong> and paste it below]
      ],
      status: :available
    },
    %{
      type: "printful",
      module: Berrypod.Providers.Printful,
      name: "Printful",
      tagline: "Premium quality with in-house fulfilment",
      features: ["In-house production", "Warehousing & branding", "Mockup generator"],
      setup_hint: "Get your API token from Printful → Settings → API",
      setup_url: "https://www.printful.com/dashboard/developer/api",
      login_url: "https://www.printful.com/auth/login",
      signup_url: "https://www.printful.com/auth/signup",
      setup_steps: [
        ~s[Go to <strong>Settings</strong> &rarr; <strong>API access</strong>],
        ~s[Click <strong>Create API key</strong>],
        ~s[Give it a name and select <strong>all scopes</strong>],
        ~s[Copy the token and paste it below]
      ],
      status: :available
    },
    %{
      type: "gelato",
      module: nil,
      name: "Gelato",
      tagline: "Local production in 30+ countries",
      status: :coming_soon
    },
    %{
      type: "prodigi",
      module: nil,
      name: "Prodigi",
      tagline: "Fine art and photo products",
      status: :coming_soon
    }
  ]

  # Build a compile-time lookup map for for_type/1
  @type_to_module Map.new(@providers, fn p -> {p.type, p[:module]} end)

  # ── Callbacks ──

  @callback provider_type() :: String.t()

  @callback test_connection(ProviderConnection.t()) :: {:ok, map()} | {:error, term()}

  @callback fetch_products(ProviderConnection.t()) :: {:ok, [map()]} | {:error, term()}

  @callback submit_order(ProviderConnection.t(), order :: map()) ::
              {:ok, %{provider_order_id: String.t()}} | {:error, term()}

  @callback get_order_status(ProviderConnection.t(), provider_order_id :: String.t()) ::
              {:ok, map()} | {:error, term()}

  @doc """
  Fetches shipping rates from the provider for the given products.

  Optional — providers that don't support shipping rate lookup can skip this.
  The sync worker checks `function_exported?/3` before calling.
  """
  @callback fetch_shipping_rates(ProviderConnection.t(), products :: [map()]) ::
              {:ok, [map()]} | {:error, term()}

  @optional_callbacks [fetch_shipping_rates: 2]

  # ── Registry ──

  @doc "Returns all providers (available and coming soon)."
  def all, do: @providers

  @doc "Returns only providers with `:available` status."
  def available, do: Enum.filter(@providers, &(&1.status == :available))

  @doc "Returns a provider metadata map by type string, or nil."
  def get(type), do: Enum.find(@providers, &(&1.type == type))

  # ── Module dispatch ──

  @doc """
  Returns the provider module for a given provider type.

  Checks `:provider_modules` application config first, allowing test
  overrides via Mox. Falls back to the `@providers` list.
  """
  def for_type(type) do
    case Application.get_env(:berrypod, :provider_modules, %{}) do
      modules when is_map(modules) ->
        case Map.get(modules, type) do
          nil -> default_for_type(type)
          module -> {:ok, module}
        end

      _ ->
        default_for_type(type)
    end
  end

  defp default_for_type(type) do
    case Map.fetch(@type_to_module, type) do
      {:ok, nil} -> {:error, :not_implemented}
      {:ok, module} -> {:ok, module}
      :error -> {:error, {:unknown_provider, type}}
    end
  end

  @doc """
  Returns the provider module for a provider connection.
  """
  def for_connection(%ProviderConnection{provider_type: type}) do
    for_type(type)
  end
end
