defmodule Berrypod.Site.SocialLink do
  use Ecto.Schema
  import Ecto.Changeset

  @primary_key {:id, :binary_id, autogenerate: true}
  @foreign_key_type :binary_id

  # Grouped by category for the editor dropdown
  @platform_groups [
    {"Social", ~w(instagram threads tiktok facebook twitter snapchat linkedin)},
    {"Video & streaming", ~w(youtube twitch vimeo kick rumble)},
    {"Music & podcasts", ~w(spotify soundcloud bandcamp applepodcasts)},
    {"Creative", ~w(pinterest behance dribbble tumblr medium)},
    {"Support & sales", ~w(patreon kofi etsy gumroad substack)},
    {"Federated", ~w(mastodon pixelfed bluesky peertube lemmy matrix)},
    {"Developer", ~w(github gitlab codeberg sourcehut reddit)},
    {"Messaging", ~w(discord telegram signal whatsapp)},
    {"Other", ~w(linktree rss website custom)}
  ]

  @platforms @platform_groups |> Enum.flat_map(fn {_, platforms} -> platforms end)

  schema "social_links" do
    field :platform, :string
    field :url, :string
    field :position, :integer, default: 0

    timestamps(type: :utc_datetime)
  end

  def platforms, do: @platforms
  def platform_groups, do: @platform_groups

  @doc """
  Normalizes a URL by trimming whitespace and adding https:// if missing.

  Returns the normalized URL string, or the original if empty/nil.
  """
  def normalize_url(nil), do: nil
  def normalize_url(""), do: ""

  def normalize_url(url) when is_binary(url) do
    url = String.trim(url)

    cond do
      url == "" -> ""
      # Preserve existing protocols (http, https, and app-specific deep links)
      String.contains?(url, "://") -> url
      # Preserve other URI schemes (mailto:, tel:, etc.)
      Regex.match?(~r/^[a-z][a-z0-9+.-]*:/i, url) -> url
      # Default to https for bare domains
      true -> "https://" <> url
    end
  end

  @doc """
  Detects the platform from a URL by matching the domain.

  Returns the platform string if detected, or "custom" for unknown domains.
  Returns nil for invalid URLs. Automatically normalizes the URL first.
  """
  def detect_platform(url) when is_binary(url) do
    url
    |> normalize_url()
    |> do_detect_platform()
  end

  def detect_platform(_), do: nil

  defp do_detect_platform(""), do: nil
  defp do_detect_platform(nil), do: nil

  # Scheme-to-platform mapping for app deep links and custom protocols
  @scheme_platforms %{
    # RSS/feeds
    "rss" => "rss",
    "feed" => "rss",
    # Social
    "instagram" => "instagram",
    "fb" => "facebook",
    "twitter" => "twitter",
    "snapchat" => "snapchat",
    "linkedin" => "linkedin",
    # Video & streaming
    "youtube" => "youtube",
    "vnd.youtube" => "youtube",
    "twitch" => "twitch",
    "vimeo" => "vimeo",
    # Music & podcasts
    "spotify" => "spotify",
    "soundcloud" => "soundcloud",
    "bandcamp" => "bandcamp",
    "podcasts" => "applepodcasts",
    "itms-podcasts" => "applepodcasts",
    # Creative
    "pinterest" => "pinterest",
    "tumblr" => "tumblr",
    # Support & sales
    "patreon" => "patreon",
    # Federated
    "matrix" => "matrix",
    # Developer
    "github" => "github",
    "github-mac" => "github",
    "github-windows" => "github",
    "x-github-client" => "github",
    "gitlab" => "gitlab",
    "reddit" => "reddit",
    # Messaging
    "discord" => "discord",
    "tg" => "telegram",
    "telegram" => "telegram",
    "sgnl" => "signal",
    "signal" => "signal",
    "whatsapp" => "whatsapp"
  }

  defp do_detect_platform(url) do
    case URI.parse(url) do
      # Scheme-based detection for app deep links
      %URI{scheme: scheme} when is_map_key(@scheme_platforms, scheme) ->
        @scheme_platforms[scheme]

      # Host-based detection (the common case)
      %URI{host: host} when is_binary(host) ->
        host
        |> String.downcase()
        |> String.replace_prefix("www.", "")
        |> detect_from_host()

      _ ->
        nil
    end
  end

  # Host-to-platform mapping for domain-based detection
  @host_platforms %{
    # Social
    "instagram.com" => "instagram",
    "threads.net" => "threads",
    "tiktok.com" => "tiktok",
    "facebook.com" => "facebook",
    "fb.com" => "facebook",
    "twitter.com" => "twitter",
    "x.com" => "twitter",
    "snapchat.com" => "snapchat",
    "linkedin.com" => "linkedin",
    # Video & streaming
    "youtube.com" => "youtube",
    "youtu.be" => "youtube",
    "twitch.tv" => "twitch",
    "vimeo.com" => "vimeo",
    "kick.com" => "kick",
    "rumble.com" => "rumble",
    # Music & podcasts
    "spotify.com" => "spotify",
    "open.spotify.com" => "spotify",
    "soundcloud.com" => "soundcloud",
    "bandcamp.com" => "bandcamp",
    "podcasts.apple.com" => "applepodcasts",
    # Creative
    "pinterest.com" => "pinterest",
    "pin.it" => "pinterest",
    "behance.net" => "behance",
    "dribbble.com" => "dribbble",
    "tumblr.com" => "tumblr",
    "medium.com" => "medium",
    # Support & sales
    "patreon.com" => "patreon",
    "ko-fi.com" => "kofi",
    "etsy.com" => "etsy",
    "gumroad.com" => "gumroad",
    "substack.com" => "substack",
    # Federated
    "mastodon.social" => "mastodon",
    "pixelfed.social" => "pixelfed",
    "bsky.app" => "bluesky",
    "lemmy.world" => "lemmy",
    "matrix.to" => "matrix",
    # Developer
    "github.com" => "github",
    "gitlab.com" => "gitlab",
    "codeberg.org" => "codeberg",
    "sr.ht" => "sourcehut",
    "reddit.com" => "reddit",
    "old.reddit.com" => "reddit",
    # Messaging
    "discord.com" => "discord",
    "discord.gg" => "discord",
    "t.me" => "telegram",
    "telegram.me" => "telegram",
    "signal.me" => "signal",
    "signal.group" => "signal",
    "wa.me" => "whatsapp",
    "whatsapp.com" => "whatsapp",
    # Other
    "linktr.ee" => "linktree"
  }

  # Subdomain suffixes for user-specific URLs (e.g., user.bandcamp.com)
  @subdomain_platforms [
    {".bandcamp.com", "bandcamp"},
    {".substack.com", "substack"},
    {".tumblr.com", "tumblr"},
    {".medium.com", "medium"}
  ]

  defp detect_from_host(host) do
    Map.get_lazy(@host_platforms, host, fn ->
      Enum.find_value(@subdomain_platforms, "custom", fn {suffix, platform} ->
        if String.ends_with?(host, suffix), do: platform
      end)
    end)
  end

  @doc false
  def changeset(social_link, attrs) do
    social_link
    |> cast(attrs, [:platform, :url, :position])
    |> validate_required([:platform])
    |> validate_inclusion(:platform, @platforms)
    |> validate_url(:url)
  end

  defp validate_url(changeset, field) do
    validate_change(changeset, field, fn _, value ->
      # Allow empty/blank URLs (for newly added links)
      if value == "" or is_nil(value) do
        []
      else
        case URI.parse(value) do
          # Accept any URL with a valid scheme (http, https, app deep links, etc.)
          %URI{scheme: scheme} when is_binary(scheme) and scheme != "" ->
            []

          _ ->
            [{field, "must be a valid URL"}]
        end
      end
    end)
  end
end
