defmodule Berrypod.Cart do
  @moduledoc """
  The Cart context.

  Manages shopping cart operations stored in session. Cart items are stored
  as a list of {variant_id, quantity} tuples for minimal session storage.
  Items are hydrated with full product/variant data when needed for display.
  """

  alias Berrypod.Products
  alias Berrypod.Products.ProductImage

  @session_key "cart"

  # =============================================================================
  # Session Operations
  # =============================================================================

  @doc """
  Gets cart items from session.

  Returns a list of {variant_id, quantity} tuples.
  """
  def get_from_session(session) do
    case Map.get(session, @session_key) do
      nil -> []
      items when is_list(items) -> items
      _ -> []
    end
  end

  @doc """
  Puts cart items in session via Plug.Conn.

  Used by the CartController to persist cart to session cookie.
  """
  def put_in_session(conn, cart_items) do
    Plug.Conn.put_session(conn, @session_key, cart_items)
  end

  # =============================================================================
  # Cart Operations
  # =============================================================================

  @doc """
  Adds an item to the cart.

  If the variant is already in the cart, increments the quantity.
  Returns the updated cart items list.
  """
  def add_item(cart_items, variant_id, quantity \\ 1)
      when is_integer(quantity) and quantity > 0 do
    case List.keyfind(cart_items, variant_id, 0) do
      nil ->
        cart_items ++ [{variant_id, quantity}]

      {^variant_id, existing_qty} ->
        List.keyreplace(cart_items, variant_id, 0, {variant_id, existing_qty + quantity})
    end
  end

  @doc """
  Updates the quantity of an item in the cart.

  If quantity is 0 or less, removes the item.
  Returns the updated cart items list.
  """
  def update_quantity(cart_items, variant_id, quantity) when is_integer(quantity) do
    if quantity <= 0 do
      remove_item(cart_items, variant_id)
    else
      case List.keyfind(cart_items, variant_id, 0) do
        nil ->
          cart_items

        {^variant_id, _} ->
          List.keyreplace(cart_items, variant_id, 0, {variant_id, quantity})
      end
    end
  end

  @doc """
  Removes an item from the cart.

  Returns the updated cart items list.
  """
  def remove_item(cart_items, variant_id) do
    List.keydelete(cart_items, variant_id, 0)
  end

  @doc """
  Gets the quantity of a specific variant in the cart.

  Returns 0 if not found.
  """
  def get_quantity(cart_items, variant_id) do
    case List.keyfind(cart_items, variant_id, 0) do
      nil -> 0
      {_, qty} -> qty
    end
  end

  # =============================================================================
  # Hydration
  # =============================================================================

  @doc """
  Hydrates cart items with full variant and product data.

  Takes a list of {variant_id, quantity} tuples and returns a list of maps
  with full display data including product name, variant options, price, and image.
  """
  def hydrate(cart_items) when is_list(cart_items) do
    variant_ids = Enum.map(cart_items, fn {id, _qty} -> id end)

    if variant_ids == [] do
      []
    else
      variants_map = Products.get_variants_with_products(variant_ids)

      cart_items
      |> Enum.map(fn {variant_id, quantity} ->
        case Map.get(variants_map, variant_id) do
          nil ->
            nil

          variant ->
            %{
              variant_id: variant.id,
              product_id: variant.product.id,
              name: variant.product.title,
              variant: format_variant_options(variant.options),
              price: variant.price,
              quantity: quantity,
              image: variant_image_url(variant.product),
              is_available: variant.is_available,
              product_discontinued: variant.product.status == "discontinued"
            }
        end
      end)
      |> Enum.reject(&is_nil/1)
    end
  end

  defp format_variant_options(options) when is_map(options) and map_size(options) > 0 do
    options
    |> Map.values()
    |> Enum.join(" / ")
  end

  defp format_variant_options(_), do: nil

  defp variant_image_url(product) do
    case product.images do
      [first | _] -> ProductImage.url(first, 400)
      _ -> nil
    end
  end

  # =============================================================================
  # Helpers
  # =============================================================================

  @doc """
  Builds the full display state for a cart.

  Takes raw cart items (list of {variant_id, quantity} tuples) and returns
  a map with hydrated items, count, and formatted subtotal. Single source
  of truth for cart state computation — used by CartHook.
  """
  def build_state(raw_cart) do
    hydrated = hydrate(raw_cart)

    %{
      items: hydrated,
      count: item_count(raw_cart),
      subtotal: format_subtotal(hydrated)
    }
  end

  @doc """
  Returns the total item count in the cart.
  """
  def item_count(cart_items) do
    Enum.reduce(cart_items, 0, fn {_, qty}, acc -> acc + qty end)
  end

  @doc """
  Calculates the subtotal from hydrated cart items.

  Returns the total in pence.
  """
  def calculate_subtotal(hydrated_items) do
    Enum.reduce(hydrated_items, 0, fn item, acc ->
      acc + item.price * item.quantity
    end)
  end

  @doc """
  Formats a price in pence as a currency string using ex_money.
  """
  def format_price(price_pence) when is_integer(price_pence) do
    price_pence
    |> Decimal.new()
    |> Decimal.div(100)
    |> then(&Money.new(:GBP, &1))
    |> Money.to_string!()
  end

  def format_price(_), do: format_price(0)

  @doc """
  Formats the subtotal from hydrated items as a GBP string.
  """
  def format_subtotal(hydrated_items) do
    hydrated_items
    |> calculate_subtotal()
    |> format_price()
  end

  @doc """
  Serializes cart items for JSON transport.

  Converts {variant_id, quantity} tuples to [variant_id, quantity] lists
  for JSON compatibility.
  """
  def serialize(cart_items) do
    Enum.map(cart_items, fn {id, qty} -> [id, qty] end)
  end

  @doc """
  Deserializes cart items from JSON transport.

  Converts [variant_id, quantity] lists back to {variant_id, quantity} tuples.
  """
  def deserialize(items) when is_list(items) do
    items
    |> Enum.map(fn
      [id, qty] when is_binary(id) and is_integer(qty) -> {id, qty}
      _ -> nil
    end)
    |> Enum.reject(&is_nil/1)
  end

  def deserialize(_), do: []
end
