defmodule BerrypodWeb.StripeWebhookController do
  use BerrypodWeb, :controller

  alias Berrypod.{ActivityLog, Orders}
  alias Berrypod.Orders.{OrderNotifier, OrderSubmissionWorker}

  require Logger

  def handle(conn, _params) do
    raw_body = conn.assigns[:raw_body] || ""
    signature = List.first(get_req_header(conn, "stripe-signature")) || ""
    signing_secret = Application.get_env(:stripity_stripe, :signing_secret) || ""

    case Stripe.Webhook.construct_event(raw_body, signature, signing_secret) do
      {:ok, %Stripe.Event{} = event} ->
        handle_event(event)
        json(conn, %{received: true})

      {:error, reason} ->
        Logger.warning("Stripe webhook verification failed: #{inspect(reason)}")

        conn
        |> put_status(401)
        |> json(%{error: "Invalid signature"})
    end
  end

  defp handle_event(%Stripe.Event{type: "checkout.session.completed", data: %{object: session}}) do
    order_id = session.metadata["order_id"]

    case Orders.get_order(order_id) do
      nil ->
        Logger.warning("Stripe webhook: order not found for id=#{order_id}")

      order ->
        payment_intent_id = session.payment_intent
        {:ok, order} = Orders.mark_paid(order, payment_intent_id)

        # Update shipping cost from Stripe (if shipping options were presented)
        order = update_shipping_cost(order, session)

        # Update shipping address if collected by Stripe
        order =
          if session.shipping_details do
            {:ok, updated} = update_shipping(order, session.shipping_details)
            updated
          else
            order
          end

        # Update customer email from Stripe session
        order =
          if session.customer_details && session.customer_details.email do
            {:ok, updated} =
              Orders.update_order(order, %{customer_email: session.customer_details.email})

            updated
          else
            order
          end

        # Reload items for the email (update_order doesn't preload)
        order = Orders.get_order(order.id)

        # Broadcast to success page via PubSub
        Phoenix.PubSub.broadcast(
          Berrypod.PubSub,
          "order:#{order.id}:status",
          {:order_paid, order}
        )

        ActivityLog.log_event(
          "order.created",
          "Order placed — #{Berrypod.Cart.format_price(order.total)} via Stripe",
          order_id: order.id,
          payload: %{
            total: order.total,
            currency: order.currency,
            payment_intent: payment_intent_id
          }
        )

        OrderNotifier.deliver_order_confirmation(order)

        # Submit to fulfilment provider
        if order.shipping_address && order.shipping_address != %{} do
          OrderSubmissionWorker.enqueue(order.id)
        else
          Logger.warning(
            "Order #{order.order_number} paid but no shipping address — manual submit needed"
          )
        end

        Logger.info("Order #{order.order_number} marked as paid")
    end
  end

  defp handle_event(%Stripe.Event{type: "checkout.session.expired", data: %{object: session}}) do
    order_id = session.metadata["order_id"]
    order = Orders.get_order(order_id)

    if order, do: Orders.mark_failed(order)

    Logger.info("Stripe checkout session expired for order #{order_id}")

    if Berrypod.Settings.abandoned_cart_recovery_enabled?() do
      maybe_create_abandoned_cart(session, order)
    end
  end

  defp handle_event(%Stripe.Event{type: type}) do
    Logger.debug("Unhandled Stripe event: #{type}")
  end

  defp maybe_create_abandoned_cart(_session, nil), do: :ok

  defp maybe_create_abandoned_cart(session, order) do
    email = session.customer_details && session.customer_details.email

    cond do
      is_nil(email) or email == "" ->
        Logger.debug("Cart recovery: no email captured for session #{session.id}")

      Orders.check_suppression(email) == :suppressed ->
        Logger.debug("Cart recovery: #{email} is suppressed")

      Orders.has_recent_paid_order?(email) ->
        Logger.debug("Cart recovery: #{email} has a recent paid order, skipping")

      not is_nil(Orders.get_abandoned_cart_by_session(session.id)) ->
        Logger.debug("Cart recovery: session #{session.id} already recorded")

      true ->
        create_cart_and_enqueue(session, order, email)
    end
  end

  defp create_cart_and_enqueue(session, order, email) do
    expired_at = DateTime.utc_now() |> DateTime.truncate(:second)

    attrs = %{
      customer_email: email,
      order_id: order.id,
      stripe_session_id: session.id,
      cart_total: order.total,
      expired_at: expired_at
    }

    case Orders.create_abandoned_cart(attrs) do
      {:ok, cart} ->
        Berrypod.Orders.AbandonedCartEmailWorker.enqueue(cart.id)

        ActivityLog.log_event(
          "abandoned_cart.created",
          "Abandoned cart — #{email}, #{Berrypod.Cart.format_price(order.total)}",
          order_id: order.id,
          payload: %{email: email, total: order.total}
        )

        Logger.info("Abandoned cart recorded for #{email}, recovery email scheduled")

      {:error, reason} ->
        Logger.warning("Failed to create abandoned cart: #{inspect(reason)}")
    end
  end

  defp update_shipping(order, shipping_details) do
    address = shipping_details.address || %{}

    shipping_address = %{
      "name" => shipping_details.name,
      "line1" => address.line1,
      "line2" => address.line2,
      "city" => address.city,
      "postal_code" => address.postal_code,
      "state" => address.state,
      "country" => address.country
    }

    Orders.update_order(order, %{shipping_address: shipping_address})
  end

  defp update_shipping_cost(order, session) do
    shipping_amount = session.shipping_cost && session.shipping_cost.amount_total

    if is_integer(shipping_amount) and shipping_amount > 0 do
      new_total = order.subtotal + shipping_amount

      case Orders.update_order(order, %{shipping_cost: shipping_amount, total: new_total}) do
        {:ok, updated} -> updated
        {:error, _} -> order
      end
    else
      order
    end
  end
end
