defmodule BerrypodWeb.Admin.Settings do
  use BerrypodWeb, :live_view

  alias Berrypod.Products
  alias Berrypod.Settings
  alias Berrypod.Stripe.Setup, as: StripeSetup

  @impl true
  def mount(_params, _session, socket) do
    user = socket.assigns.current_scope.user

    {:ok,
     socket
     |> assign(:page_title, "Settings")
     |> assign(:site_live, Settings.site_live?())
     |> assign(:cart_recovery_enabled, Settings.abandoned_cart_recovery_enabled?())
     |> assign(:from_address, Settings.get_setting("email_from_address") || user.email)
     |> assign(:from_address_status, :idle)
     |> assign(:signing_secret_status, :idle)
     |> assign_stripe_state()
     |> assign_products_state()
     |> assign_url_prefixes()}
  end

  defp assign_url_prefixes(socket) do
    socket
    |> assign(:products_prefix, Settings.get_url_prefix(:products))
    |> assign(:collections_prefix, Settings.get_url_prefix(:collections))
    |> assign(:prefix_status, :idle)
  end

  # -- Stripe assigns --

  defp assign_stripe_state(socket) do
    has_key = Settings.has_secret?("stripe_api_key")
    has_signing = Settings.has_secret?("stripe_signing_secret")

    status =
      cond do
        !has_key -> :not_configured
        has_key && StripeSetup.localhost?() -> :connected_localhost
        true -> :connected
      end

    socket
    |> assign(:stripe_status, status)
    |> assign(:stripe_api_key_hint, Settings.secret_hint("stripe_api_key"))
    |> assign(:stripe_signing_secret_hint, Settings.secret_hint("stripe_signing_secret"))
    |> assign(:stripe_webhook_url, StripeSetup.webhook_url())
    |> assign(:stripe_has_signing_secret, has_signing)
    |> assign(:connect_form, to_form(%{"api_key" => ""}, as: :stripe))
    |> assign(:secret_form, to_form(%{"signing_secret" => ""}, as: :webhook))
    |> assign(:stripe_advanced_open, false)
    |> assign(:connecting, false)
  end

  # -- Products assigns --

  defp assign_products_state(socket) do
    connections = Products.list_provider_connections()

    connection_info =
      case connections do
        [conn | _] ->
          product_count = Products.count_products_for_connection(conn.id)
          %{connection: conn, product_count: product_count}

        [] ->
          nil
      end

    assign(socket, :provider, connection_info)
  end

  # -- Events: shop status --

  @impl true
  def handle_event("toggle_site_live", _params, socket) do
    new_value = !socket.assigns.site_live
    {:ok, _} = Settings.set_site_live(new_value)

    message = if new_value, do: "Shop is now live", else: "Shop taken offline"

    {:noreply,
     socket
     |> assign(:site_live, new_value)
     |> put_flash(:info, message)}
  end

  # -- Events: cart recovery --

  def handle_event("toggle_cart_recovery", _params, socket) do
    new_value = !socket.assigns.cart_recovery_enabled
    {:ok, _} = Settings.set_abandoned_cart_recovery(new_value)

    message =
      if new_value,
        do: "Cart recovery emails enabled",
        else: "Cart recovery emails disabled"

    {:noreply,
     socket
     |> assign(:cart_recovery_enabled, new_value)
     |> put_flash(:info, message)}
  end

  # -- Events: from address --

  def handle_event("change_from_address", _params, socket) do
    {:noreply, assign(socket, :from_address_status, :idle)}
  end

  def handle_event("save_from_address", %{"from_address" => address}, socket) do
    address = String.trim(address)

    if address != "" do
      Settings.put_setting("email_from_address", address)

      {:noreply,
       socket
       |> assign(:from_address, address)
       |> assign(:from_address_status, :saved)}
    else
      {:noreply, put_flash(socket, :error, "From address can't be blank")}
    end
  end

  # -- Events: URL prefixes --

  def handle_event("change_prefix", _params, socket) do
    {:noreply, assign(socket, :prefix_status, :idle)}
  end

  def handle_event("save_prefixes", %{"prefixes" => params}, socket) do
    products_prefix = params["products"] || ""
    collections_prefix = params["collections"] || ""

    errors = []

    # Update products prefix if changed
    errors =
      if products_prefix != socket.assigns.products_prefix do
        case Settings.update_url_prefix(:products, products_prefix) do
          {:ok, _} -> errors
          {:error, reason} -> [{:products, reason} | errors]
        end
      else
        errors
      end

    # Update collections prefix if changed
    errors =
      if collections_prefix != socket.assigns.collections_prefix do
        case Settings.update_url_prefix(:collections, collections_prefix) do
          {:ok, _} -> errors
          {:error, reason} -> [{:collections, reason} | errors]
        end
      else
        errors
      end

    if errors == [] do
      {:noreply,
       socket
       |> assign_url_prefixes()
       |> assign(:prefix_status, :saved)}
    else
      error_message = format_prefix_errors(errors)
      {:noreply, put_flash(socket, :error, error_message)}
    end
  end

  # -- Events: Stripe --

  def handle_event("connect_stripe", %{"stripe" => %{"api_key" => api_key}}, socket) do
    if api_key == "" do
      {:noreply, put_flash(socket, :error, "Please enter your Stripe secret key")}
    else
      socket = assign(socket, :connecting, true)

      case StripeSetup.connect(api_key) do
        {:ok, :webhook_created} ->
          socket =
            socket
            |> assign_stripe_state()
            |> put_flash(:info, "Stripe connected and webhook endpoint created")

          {:noreply, socket}

        {:ok, :localhost} ->
          socket =
            socket
            |> assign_stripe_state()
            |> put_flash(
              :info,
              "API key saved. Enter a webhook signing secret below for local testing."
            )

          {:noreply, socket}

        {:error, message} ->
          socket =
            socket
            |> assign(:connecting, false)
            |> put_flash(:error, "Stripe connection failed: #{message}")

          {:noreply, socket}
      end
    end
  end

  def handle_event("disconnect_stripe", _params, socket) do
    StripeSetup.disconnect()

    socket =
      socket
      |> assign_stripe_state()
      |> put_flash(:info, "Stripe disconnected")

    {:noreply, socket}
  end

  def handle_event("change_signing_secret", _params, socket) do
    {:noreply, assign(socket, :signing_secret_status, :idle)}
  end

  def handle_event("save_signing_secret", %{"webhook" => %{"signing_secret" => secret}}, socket) do
    if secret == "" do
      {:noreply, put_flash(socket, :error, "Please enter a signing secret")}
    else
      StripeSetup.save_signing_secret(secret)

      socket =
        socket
        |> assign_stripe_state()
        |> assign(:signing_secret_status, :saved)

      {:noreply, socket}
    end
  end

  def handle_event("toggle_stripe_advanced", _params, socket) do
    {:noreply, assign(socket, :stripe_advanced_open, !socket.assigns.stripe_advanced_open)}
  end

  # -- Events: products --

  def handle_event("sync", %{"id" => id}, socket) do
    connection = Products.get_provider_connection!(id)

    case Products.enqueue_sync(connection) do
      {:ok, _job} ->
        {:noreply,
         socket
         |> assign_products_state()
         |> put_flash(:info, "Sync started for #{connection.name}")}

      {:error, _reason} ->
        {:noreply, put_flash(socket, :error, "Failed to start sync")}
    end
  end

  def handle_event("delete_connection", %{"id" => id}, socket) do
    connection = Products.get_provider_connection!(id)
    {:ok, _} = Products.delete_provider_connection(connection)

    {:noreply,
     socket
     |> assign_products_state()
     |> put_flash(:info, "Provider connection deleted")}
  end

  # -- Render --

  @impl true
  def render(assigns) do
    ~H"""
    <div class="admin-settings">
      <.header>
        Settings
      </.header>

      <%!-- Shop status --%>
      <section class="admin-section">
        <div class="admin-section-header">
          <h2 class="admin-section-title">Shop status</h2>
          <%= if @site_live do %>
            <.status_pill color="green">
              <.icon name="hero-check-circle-mini" class="size-3" /> Live
            </.status_pill>
          <% else %>
            <.status_pill color="zinc">Offline</.status_pill>
          <% end %>
        </div>
        <p class="admin-section-desc">
          <%= if @site_live do %>
            Your shop is visible to the public.
          <% else %>
            Your shop is offline. Visitors see a "coming soon" page.
          <% end %>
        </p>
        <div class="admin-section-body">
          <button
            phx-click="toggle_site_live"
            class={[
              "admin-btn admin-btn-sm",
              if(@site_live, do: "admin-btn-outline", else: "admin-btn-primary")
            ]}
          >
            <%= if @site_live do %>
              <.icon name="hero-eye-slash-mini" class="size-4" /> Take offline
            <% else %>
              <.icon name="hero-eye-mini" class="size-4" /> Go live
            <% end %>
          </button>
        </div>
      </section>

      <%!-- Payments --%>
      <section class="admin-section">
        <div class="admin-section-header">
          <h2 class="admin-section-title">Payments</h2>
          <%= case @stripe_status do %>
            <% :connected -> %>
              <.status_pill color="green">
                <.icon name="hero-check-circle-mini" class="size-3" /> Connected
              </.status_pill>
            <% :connected_localhost -> %>
              <.status_pill color="amber">
                <.icon name="hero-exclamation-triangle-mini" class="size-3" /> Dev mode
              </.status_pill>
            <% :not_configured -> %>
              <.status_pill color="zinc">Not connected</.status_pill>
          <% end %>
        </div>

        <%= if @stripe_status == :not_configured do %>
          <.stripe_setup_form connect_form={@connect_form} connecting={@connecting} />
        <% else %>
          <.stripe_connected_view
            stripe_status={@stripe_status}
            stripe_api_key_hint={@stripe_api_key_hint}
            stripe_webhook_url={@stripe_webhook_url}
            stripe_signing_secret_hint={@stripe_signing_secret_hint}
            stripe_has_signing_secret={@stripe_has_signing_secret}
            secret_form={@secret_form}
            advanced_open={@stripe_advanced_open}
            signing_secret_status={@signing_secret_status}
          />
        <% end %>
      </section>

      <%!-- Products --%>
      <section class="admin-section">
        <div class="admin-section-header">
          <h2 class="admin-section-title">Products</h2>
          <%= if @provider do %>
            <.status_pill color="green">
              <.icon name="hero-check-circle-mini" class="size-3" /> Connected
            </.status_pill>
          <% else %>
            <.status_pill color="zinc">Not connected</.status_pill>
          <% end %>
        </div>

        <%= if @provider do %>
          <.provider_connected provider={@provider} />
        <% else %>
          <div class="admin-section-body">
            <p class="admin-section-desc admin-section-desc-flush">
              Connect a print-on-demand provider to import products into your shop.
            </p>
            <div class="admin-section-body">
              <.link navigate={~p"/admin/providers"} class="admin-btn admin-btn-primary admin-btn-sm">
                <.icon name="hero-plus-mini" class="size-4" /> Connect a provider
              </.link>
            </div>
          </div>
        <% end %>
      </section>

      <%!-- Cart recovery --%>
      <section class="admin-section">
        <div class="admin-section-header">
          <h2 class="admin-section-title">Cart recovery</h2>
          <%= if @cart_recovery_enabled do %>
            <.status_pill color="green">
              <.icon name="hero-check-circle-mini" class="size-3" /> On
            </.status_pill>
          <% else %>
            <.status_pill color="zinc">Off</.status_pill>
          <% end %>
        </div>
        <p class="admin-section-desc">
          When on, customers who entered their email at Stripe checkout but didn't complete
          payment receive a single plain-text recovery email one hour later.
          No tracking pixels. One email, never more.
        </p>
        <%= if @cart_recovery_enabled do %>
          <p class="admin-section-desc admin-text-warning">
            Make sure your privacy policy mentions that a single recovery email may be sent,
            and that customers can unsubscribe at any time.
          </p>
        <% end %>
        <div class="admin-section-body">
          <button
            phx-click="toggle_cart_recovery"
            class={[
              "admin-btn admin-btn-sm",
              if(@cart_recovery_enabled, do: "admin-btn-outline", else: "admin-btn-primary")
            ]}
          >
            {if @cart_recovery_enabled, do: "Turn off", else: "Turn on"}
          </button>
        </div>
      </section>

      <%!-- From address --%>
      <section class="admin-section">
        <h2 class="admin-section-title">From address</h2>
        <p class="admin-section-desc">
          The sender address on all emails from your shop.
        </p>
        <div class="admin-section-body">
          <form
            action={~p"/admin/settings/from-address"}
            method="post"
            phx-change="change_from_address"
            phx-submit="save_from_address"
            class="admin-row admin-row-lg"
          >
            <input type="hidden" name="_csrf_token" value={Phoenix.Controller.get_csrf_token()} />
            <.input
              name="from_address"
              value={@from_address}
              type="email"
              placeholder="noreply@yourshop.com"
            />
            <.button phx-disable-with="Saving...">Save</.button>
            <.inline_feedback status={@from_address_status} />
          </form>
        </div>
      </section>

      <%!-- URL prefixes --%>
      <section class="admin-section">
        <h2 class="admin-section-title">URL prefixes</h2>
        <p class="admin-section-desc">
          Customise the URL structure for products and collections.
          Old URLs will automatically redirect to the new ones.
        </p>
        <div class="admin-section-body">
          <form
            phx-change="change_prefix"
            phx-submit="save_prefixes"
            class="admin-stack"
          >
            <div class="admin-row admin-row-lg">
              <label class="admin-label admin-label-inline" for="prefix-products">
                Products
              </label>
              <div class="admin-row admin-row-sm">
                <span class="admin-text-secondary">/</span>
                <.input
                  name="prefixes[products]"
                  id="prefix-products"
                  value={@products_prefix}
                  placeholder="products"
                  pattern="[a-z0-9-]+"
                />
                <span class="admin-text-secondary">/</span>
                <span class="admin-text-tertiary">product-slug</span>
              </div>
            </div>

            <div class="admin-row admin-row-lg">
              <label class="admin-label admin-label-inline" for="prefix-collections">
                Collections
              </label>
              <div class="admin-row admin-row-sm">
                <span class="admin-text-secondary">/</span>
                <.input
                  name="prefixes[collections]"
                  id="prefix-collections"
                  value={@collections_prefix}
                  placeholder="collections"
                  pattern="[a-z0-9-]+"
                />
                <span class="admin-text-secondary">/</span>
                <span class="admin-text-tertiary">collection-slug</span>
              </div>
            </div>

            <p class="admin-help-text">
              Use only lowercase letters, numbers, and hyphens.
            </p>

            <div class="admin-form-actions-sm">
              <.button phx-disable-with="Saving...">Save</.button>
              <.inline_feedback status={@prefix_status} />
            </div>
          </form>
        </div>
      </section>
    </div>
    """
  end

  # -- Function components --

  attr :color, :string, required: true
  slot :inner_block, required: true

  defp status_pill(assigns) do
    modifier =
      case assigns.color do
        "green" -> "admin-status-pill-green"
        "amber" -> "admin-status-pill-amber"
        _ -> "admin-status-pill-zinc"
      end

    assigns = assign(assigns, :modifier, modifier)

    ~H"""
    <span class={["admin-status-pill", @modifier]}>
      {render_slot(@inner_block)}
    </span>
    """
  end

  attr :provider, :map, required: true

  defp provider_connected(assigns) do
    conn = assigns.provider.connection

    assigns =
      assigns
      |> assign(:connection, conn)
      |> assign(:product_count, assigns.provider.product_count)
      |> assign(:syncing, conn.sync_status == "syncing")
      |> assign(:provider_label, String.capitalize(conn.provider_type))

    ~H"""
    <div class="admin-section-body">
      <dl class="admin-dl">
        <div class="admin-dl-row">
          <dt class="admin-dl-term">Provider</dt>
          <dd class="admin-dl-value">{@provider_label}</dd>
        </div>
        <div class="admin-dl-row">
          <dt class="admin-dl-term">Shop</dt>
          <dd class="admin-dl-value">{@connection.name}</dd>
        </div>
        <div class="admin-dl-row">
          <dt class="admin-dl-term">Products</dt>
          <dd class="admin-dl-value">{@product_count}</dd>
        </div>
        <div class="admin-dl-row">
          <dt class="admin-dl-term">Last synced</dt>
          <dd class="admin-dl-value">
            <%= if @connection.last_synced_at do %>
              {format_relative_time(@connection.last_synced_at)}
            <% else %>
              <span class="admin-text-warning">Never</span>
            <% end %>
          </dd>
        </div>
      </dl>

      <div class="admin-cluster admin-section-body">
        <button
          phx-click="sync"
          phx-value-id={@connection.id}
          disabled={@syncing}
          class="admin-btn admin-btn-outline admin-btn-sm"
        >
          <.icon
            name="hero-arrow-path"
            class={if @syncing, do: "size-4 animate-spin", else: "size-4"}
          />
          {if @syncing, do: "Syncing...", else: "Sync products"}
        </button>
        <.link
          navigate={~p"/admin/providers/#{@connection.id}/edit"}
          class="admin-btn admin-btn-outline admin-btn-sm"
        >
          <.icon name="hero-cog-6-tooth" class="size-4" /> Settings
        </.link>
        <button
          phx-click="delete_connection"
          phx-value-id={@connection.id}
          data-confirm={"Disconnect from #{@provider_label}? Your synced products will remain in your shop."}
          class="admin-link-danger"
        >
          Disconnect
        </button>
      </div>
    </div>
    """
  end

  defp stripe_setup_form(assigns) do
    ~H"""
    <div class="admin-section-body">
      <p class="admin-section-desc admin-section-desc-flush">
        To accept payments, connect your Stripe account by entering your secret key.
        You can find it in your
        <.external_link href="https://dashboard.stripe.com/apikeys" class="admin-link">
          Stripe dashboard
        </.external_link>
        under Developers &rarr; API keys.
      </p>

      <.form for={@connect_form} phx-submit="connect_stripe" class="admin-card-spaced">
        <.input
          field={@connect_form[:api_key]}
          type="password"
          label="Secret key"
          autocomplete="off"
          placeholder="sk_test_... or sk_live_..."
        />
        <p class="admin-help-text">
          Starts with <code>sk_test_</code> (test mode) or <code>sk_live_</code> (live mode).
          This key is encrypted at rest in the database.
        </p>
        <div class="admin-section-body">
          <.button phx-disable-with="Connecting...">
            Connect Stripe
          </.button>
        </div>
      </.form>
    </div>
    """
  end

  defp stripe_connected_view(assigns) do
    ~H"""
    <div class="admin-stack admin-section-body">
      <dl class="admin-dl">
        <div class="admin-dl-row">
          <dt class="admin-dl-term">API key</dt>
          <dd class="admin-dl-value"><code>{@stripe_api_key_hint}</code></dd>
        </div>
        <div class="admin-dl-row">
          <dt class="admin-dl-term">Webhook URL</dt>
          <dd class="admin-dl-value">
            <code class="admin-code-break">{@stripe_webhook_url}</code>
          </dd>
        </div>
        <div class="admin-dl-row">
          <dt class="admin-dl-term">Webhook secret</dt>
          <dd class="admin-dl-value">
            <%= if @stripe_has_signing_secret do %>
              <code>{@stripe_signing_secret_hint}</code>
            <% else %>
              <span class="admin-text-warning">Not set</span>
            <% end %>
          </dd>
        </div>
      </dl>

      <%= if @stripe_status == :connected_localhost do %>
        <div class="admin-info-box admin-info-box-amber">
          <p>
            Stripe can't reach localhost for webhooks. For local testing, run the Stripe CLI:
          </p>
          <pre>stripe listen --forward-to localhost:4000/webhooks/stripe</pre>
          <p class="admin-help-text admin-text-warning">
            The CLI will output a signing secret starting with <code>whsec_</code>. Enter it below.
          </p>
        </div>

        <.form
          for={@secret_form}
          action={~p"/admin/settings/stripe/signing-secret"}
          method="post"
          phx-change="change_signing_secret"
          phx-submit="save_signing_secret"
        >
          <.input
            field={@secret_form[:signing_secret]}
            type="password"
            label="Webhook signing secret"
            autocomplete="off"
            placeholder="whsec_..."
          />
          <div class="admin-form-actions-sm">
            <.button phx-disable-with="Saving...">Save signing secret</.button>
            <.inline_feedback status={@signing_secret_status} />
          </div>
        </.form>
      <% else %>
        <div class="admin-separator">
          <button
            phx-click="toggle_stripe_advanced"
            class="admin-link-subtle admin-row admin-row-sm"
          >
            <.icon
              name={if @advanced_open, do: "hero-chevron-down-mini", else: "hero-chevron-right-mini"}
              class="size-4"
            /> Advanced
          </button>

          <%= if @advanced_open do %>
            <div class="admin-form-actions-sm">
              <p class="admin-text-tertiary">
                Override the webhook signing secret if you need to use a custom endpoint or the Stripe CLI.
              </p>
              <.form
                for={@secret_form}
                action={~p"/admin/settings/stripe/signing-secret"}
                method="post"
                phx-change="change_signing_secret"
                phx-submit="save_signing_secret"
              >
                <.input
                  field={@secret_form[:signing_secret]}
                  type="password"
                  label="Webhook signing secret"
                  autocomplete="off"
                  placeholder="whsec_..."
                />
                <div class="admin-form-actions-sm">
                  <.button phx-disable-with="Saving...">Save signing secret</.button>
                  <.inline_feedback status={@signing_secret_status} />
                </div>
              </.form>
            </div>
          <% end %>
        </div>
      <% end %>

      <div class="admin-separator-lg">
        <button
          phx-click="disconnect_stripe"
          data-confirm="This will remove your Stripe API key and delete the webhook endpoint. Are you sure?"
          class="admin-link-danger"
        >
          Disconnect Stripe
        </button>
      </div>
    </div>
    """
  end

  defp format_relative_time(datetime) do
    diff = DateTime.diff(DateTime.utc_now(), datetime, :second)

    cond do
      diff < 60 -> "just now"
      diff < 3600 -> "#{div(diff, 60)} min ago"
      diff < 86400 -> "#{div(diff, 3600)} hours ago"
      true -> "#{div(diff, 86400)} days ago"
    end
  end

  defp format_prefix_errors(errors) do
    Enum.map_join(errors, "; ", fn {field, reason} ->
      field_name = if field == :products, do: "Products", else: "Collections"

      message =
        case reason do
          :empty_prefix -> "can't be blank"
          :invalid_format -> "must contain only letters, numbers, and hyphens"
          :reserved_prefix -> "is reserved"
          _ -> "is invalid"
        end

      "#{field_name} prefix #{message}"
    end)
  end
end
