defmodule Berrypod.Site do
  @moduledoc """
  The Site context for managing site-wide content.

  This includes navigation items, social links, announcement bar,
  and footer content — everything that appears across all pages.
  """

  import Ecto.Query, warn: false
  alias Berrypod.Repo
  alias Berrypod.Site.{NavItem, SocialLink}
  alias Berrypod.Settings

  # ── Navigation items ───────────────────────────────────────────────

  @doc """
  Lists all navigation items for a location, ordered by position.
  """
  def list_nav_items(location) when location in [:header, :footer, "header", "footer"] do
    location = to_string(location)

    NavItem
    |> where([n], n.location == ^location)
    |> order_by([n], asc: n.position)
    |> preload(:page)
    |> Repo.all()
  end

  @doc """
  Gets a single nav item by ID.
  """
  def get_nav_item!(id), do: Repo.get!(NavItem, id)

  @doc """
  Creates a nav item.
  """
  def create_nav_item(attrs \\ %{}) do
    %NavItem{}
    |> NavItem.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates a nav item.
  """
  def update_nav_item(%NavItem{} = nav_item, attrs) do
    nav_item
    |> NavItem.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a nav item.
  """
  def delete_nav_item(%NavItem{} = nav_item) do
    Repo.delete(nav_item)
  end

  @doc """
  Returns a changeset for tracking nav item changes.
  """
  def change_nav_item(%NavItem{} = nav_item, attrs \\ %{}) do
    NavItem.changeset(nav_item, attrs)
  end

  @doc """
  Reorders nav items by updating positions.

  Takes a list of nav item IDs in the desired order.
  """
  def reorder_nav_items(ids) when is_list(ids) do
    Repo.transaction(fn ->
      ids
      |> Enum.with_index()
      |> Enum.each(fn {id, position} ->
        from(n in NavItem, where: n.id == ^id)
        |> Repo.update_all(set: [position: position])
      end)
    end)
  end

  @doc """
  Returns nav items formatted for shop components.

  Converts NavItem structs to the map format expected by layout components.
  """
  def nav_items_for_shop(location) do
    list_nav_items(location)
    |> Enum.map(&nav_item_to_map/1)
  end

  defp nav_item_to_map(%NavItem{} = item) do
    # When page_id is set, resolve URL dynamically via R module
    # This ensures nav links stay up to date when page URLs change
    href = resolve_nav_item_url(item)

    %{
      "label" => item.label,
      "href" => href,
      "slug" => slug_from_url(href),
      "page_id" => item.page_id
    }
  end

  defp resolve_nav_item_url(%NavItem{page: %Berrypod.Pages.Page{} = page}) do
    BerrypodWeb.R.page(page.slug)
  end

  defp resolve_nav_item_url(%NavItem{url: "/" <> slug}) when slug != "" do
    # Internal page link stored as URL - resolve through R module
    # This handles cases where page_id isn't set but URL points to a page
    # Strip any trailing path segments (e.g., /collections/all -> collections)
    base_slug = slug |> String.split("/") |> List.first()

    # Route system pages through R module to get custom URL slugs
    if Berrypod.Pages.Page.system_slug?(base_slug) do
      BerrypodWeb.R.page(base_slug)
    else
      # Check if this is a custom page with a url_slug
      case Berrypod.Pages.get_page(base_slug) do
        %{url_slug: url_slug} when not is_nil(url_slug) -> "/" <> url_slug
        _ -> "/" <> slug
      end
    end
  end

  defp resolve_nav_item_url(%NavItem{url: url}), do: url

  defp slug_from_url("/"), do: "home"
  defp slug_from_url("/collections" <> _), do: "collection"
  defp slug_from_url("/products" <> _), do: "pdp"
  defp slug_from_url("/" <> rest), do: String.split(rest, "/") |> List.first() || ""
  defp slug_from_url(_), do: ""

  # ── Social links ───────────────────────────────────────────────────

  @doc """
  Lists all social links, ordered by position.
  """
  def list_social_links do
    SocialLink
    |> order_by([s], asc: s.position)
    |> Repo.all()
  end

  @doc """
  Gets a single social link by ID.
  """
  def get_social_link!(id), do: Repo.get!(SocialLink, id)

  @doc """
  Creates a social link.
  """
  def create_social_link(attrs \\ %{}) do
    %SocialLink{}
    |> SocialLink.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Updates a social link.
  """
  def update_social_link(%SocialLink{} = social_link, attrs) do
    social_link
    |> SocialLink.changeset(attrs)
    |> Repo.update()
  end

  @doc """
  Deletes a social link.
  """
  def delete_social_link(%SocialLink{} = social_link) do
    Repo.delete(social_link)
  end

  @doc """
  Returns a changeset for tracking social link changes.
  """
  def change_social_link(%SocialLink{} = social_link, attrs \\ %{}) do
    SocialLink.changeset(social_link, attrs)
  end

  @doc """
  Reorders social links by updating positions.
  """
  def reorder_social_links(ids) when is_list(ids) do
    Repo.transaction(fn ->
      ids
      |> Enum.with_index()
      |> Enum.each(fn {id, position} ->
        from(s in SocialLink, where: s.id == ^id)
        |> Repo.update_all(set: [position: position])
      end)
    end)
  end

  @doc """
  Returns social links formatted for shop components.

  Filters out links with empty URLs (incomplete entries from the editor).
  """
  def social_links_for_shop do
    list_social_links()
    |> Enum.reject(fn link -> is_nil(link.url) or link.url == "" end)
    |> Enum.map(fn link ->
      %{
        platform: String.to_atom(link.platform),
        url: link.url,
        label: platform_label(link.platform)
      }
    end)
  end

  defp platform_label("instagram"), do: "Instagram"
  defp platform_label("pinterest"), do: "Pinterest"
  defp platform_label("tiktok"), do: "TikTok"
  defp platform_label("facebook"), do: "Facebook"
  defp platform_label("twitter"), do: "Twitter"
  defp platform_label("youtube"), do: "YouTube"
  defp platform_label("patreon"), do: "Patreon"
  defp platform_label("kofi"), do: "Ko-fi"
  defp platform_label("etsy"), do: "Etsy"
  defp platform_label("gumroad"), do: "Gumroad"
  defp platform_label("bandcamp"), do: "Bandcamp"
  defp platform_label("mastodon"), do: "Mastodon"
  defp platform_label("pixelfed"), do: "Pixelfed"
  defp platform_label("bluesky"), do: "Bluesky"
  defp platform_label("peertube"), do: "PeerTube"
  defp platform_label("lemmy"), do: "Lemmy"
  defp platform_label("matrix"), do: "Matrix"
  defp platform_label("github"), do: "GitHub"
  defp platform_label("gitlab"), do: "GitLab"
  defp platform_label("codeberg"), do: "Codeberg"
  defp platform_label("sourcehut"), do: "SourceHut"
  defp platform_label(other), do: String.capitalize(other)

  # ── Announcement bar ───────────────────────────────────────────────

  @doc """
  Gets the announcement bar text.
  """
  def announcement_text do
    Settings.get_setting("announcement_text", "")
  end

  @doc """
  Sets the announcement bar text.
  """
  def set_announcement_text(text) when is_binary(text) do
    Settings.put_setting("announcement_text", text)
  end

  @doc """
  Gets the announcement bar link URL.
  """
  def announcement_link do
    Settings.get_setting("announcement_link", "")
  end

  @doc """
  Sets the announcement bar link URL.
  """
  def set_announcement_link(url) when is_binary(url) do
    Settings.put_setting("announcement_link", url)
  end

  @doc """
  Gets the announcement bar style (info, sale, warning).
  """
  def announcement_style do
    Settings.get_setting("announcement_style", "info")
  end

  @doc """
  Sets the announcement bar style.
  """
  def set_announcement_style(style) when style in ["info", "sale", "warning"] do
    Settings.put_setting("announcement_style", style)
  end

  # ── Footer content ─────────────────────────────────────────────────

  @doc """
  Gets the footer about text.
  """
  def footer_about do
    Settings.get_setting("footer_about", "")
  end

  @doc """
  Sets the footer about text.
  """
  def set_footer_about(text) when is_binary(text) do
    Settings.put_setting("footer_about", text)
  end

  @doc """
  Gets the footer copyright text.

  Returns empty string if not set (caller should generate default).
  """
  def footer_copyright do
    Settings.get_setting("footer_copyright", "")
  end

  @doc """
  Sets the footer copyright text.

  Pass empty string to use auto-generated copyright.
  """
  def set_footer_copyright(text) when is_binary(text) do
    Settings.put_setting("footer_copyright", text)
  end

  @doc """
  Returns whether the newsletter signup should be shown in the footer.
  """
  def show_newsletter? do
    Settings.get_setting("show_newsletter", true) == true
  end

  @doc """
  Sets whether the newsletter signup should be shown in the footer.
  """
  def set_show_newsletter(show?) when is_boolean(show?) do
    Settings.put_setting("show_newsletter", show?, "boolean")
  end

  # ── Bulk updates ───────────────────────────────────────────────────

  @doc """
  Updates the announcement bar settings.
  """
  def put_announcement(text, link, style) do
    set_announcement_text(text)
    set_announcement_link(link)
    set_announcement_style(style)
    :ok
  end

  @doc """
  Updates the footer content settings.
  """
  def put_footer_content(about, copyright, show_newsletter) do
    set_footer_about(about)
    set_footer_copyright(copyright)
    set_show_newsletter(show_newsletter)
    :ok
  end

  @doc """
  Updates multiple site settings at once.

  Accepts a map with keys like :announcement_text, :footer_about, etc.
  """
  def update_settings(attrs) when is_map(attrs) do
    Enum.each(attrs, fn
      {:announcement_text, value} -> set_announcement_text(value || "")
      {:announcement_link, value} -> set_announcement_link(value || "")
      {:announcement_style, value} -> set_announcement_style(value || "info")
      {:footer_about, value} -> set_footer_about(value || "")
      {:footer_copyright, value} -> set_footer_copyright(value || "")
      {:show_newsletter, value} -> set_show_newsletter(value == true)
      _ -> :ok
    end)

    :ok
  end

  @doc """
  Returns all site settings as a map.
  """
  def get_settings do
    %{
      announcement_text: announcement_text(),
      announcement_link: announcement_link(),
      announcement_style: announcement_style(),
      footer_about: footer_about(),
      footer_copyright: footer_copyright(),
      show_newsletter: show_newsletter?()
    }
  end

  # ── Defaults ──────────────────────────────────────────────────────

  @default_header_nav [
    %{label: "Home", url: "/", position: 0},
    %{label: "Shop", url: "/collections/all", position: 1},
    %{label: "About", url: "/about", position: 2},
    %{label: "Contact", url: "/contact", position: 3}
  ]

  @default_footer_nav [
    %{label: "Delivery & returns", url: "/delivery", position: 0},
    %{label: "Privacy policy", url: "/privacy", position: 1},
    %{label: "Terms of service", url: "/terms", position: 2},
    %{label: "Contact", url: "/contact", position: 3}
  ]

  @default_social_links [
    %{platform: "instagram", url: "https://instagram.com", position: 0},
    %{platform: "bluesky", url: "https://bsky.app", position: 1}
  ]

  @doc """
  Returns the default header navigation items in shop format.

  Used for previews and error pages when database may be empty.
  """
  def default_header_nav do
    [
      %{"label" => "Home", "href" => "/", "slug" => "home"},
      %{
        "label" => "Shop",
        "href" => "/collections/all",
        "slug" => "collection",
        "active_slugs" => ["collection", "pdp"]
      },
      %{"label" => "About", "href" => "/about", "slug" => "about"},
      %{"label" => "Contact", "href" => "/contact", "slug" => "contact"}
    ]
  end

  @doc """
  Returns the default footer navigation items in shop format.

  Used for previews and error pages when database may be empty.
  """
  def default_footer_nav do
    [
      %{"label" => "Delivery & returns", "href" => "/delivery", "slug" => "delivery"},
      %{"label" => "Privacy policy", "href" => "/privacy", "slug" => "privacy"},
      %{"label" => "Terms of service", "href" => "/terms", "slug" => "terms"},
      %{"label" => "Contact", "href" => "/contact", "slug" => "contact"}
    ]
  end

  # ── Seeding ──────────────────────────────────────────────────────────

  @doc """
  Seeds default navigation items and social links if none exist.

  Safe to call multiple times — only inserts if tables are empty.
  """
  def seed_defaults do
    seed_nav_items()
    seed_social_links()
    :ok
  end

  defp seed_nav_items do
    if Repo.aggregate(NavItem, :count) == 0 do
      now = DateTime.utc_now() |> DateTime.truncate(:second)

      header_items =
        Enum.map(@default_header_nav, fn item ->
          Map.merge(item, %{
            id: Ecto.UUID.generate(),
            location: "header",
            inserted_at: now,
            updated_at: now
          })
        end)

      footer_items =
        Enum.map(@default_footer_nav, fn item ->
          Map.merge(item, %{
            id: Ecto.UUID.generate(),
            location: "footer",
            inserted_at: now,
            updated_at: now
          })
        end)

      Repo.insert_all(NavItem, header_items ++ footer_items)
    end
  end

  defp seed_social_links do
    if Repo.aggregate(SocialLink, :count) == 0 do
      now = DateTime.utc_now() |> DateTime.truncate(:second)

      links =
        Enum.map(@default_social_links, fn item ->
          Map.merge(item, %{
            id: Ecto.UUID.generate(),
            inserted_at: now,
            updated_at: now
          })
        end)

      Repo.insert_all(SocialLink, links)
    end
  end
end
