defmodule ToweropsWeb.TimeHelpers do
  @moduledoc """
  Helper functions for formatting time and date values with timezone support.
  """

  # Common timezone offsets in seconds
  # Note: These are standard time offsets. DST handling would require a full timezone database.
  @timezone_offsets %{
    "UTC" => 0,
    "America/New_York" => -18_000,
    # EST (UTC-5)
    "America/Chicago" => -21_600,
    # CST (UTC-6)
    "America/Denver" => -25_200,
    # MST (UTC-7)
    "America/Los_Angeles" => -28_800,
    # PST (UTC-8)
    "America/Phoenix" => -25_200,
    # MST (no DST)
    "Europe/London" => 0,
    # GMT (UTC+0)
    "Europe/Paris" => 3600,
    # CET (UTC+1)
    "Europe/Berlin" => 3600,
    # CET (UTC+1)
    "Asia/Tokyo" => 32_400,
    # JST (UTC+9)
    "Asia/Shanghai" => 28_800,
    # CST (UTC+8)
    "Asia/Dubai" => 14_400,
    # GST (UTC+4)
    "Australia/Sydney" => 36_000
    # AEST (UTC+10)
  }

  @timezone_abbrs %{
    "UTC" => "UTC",
    "America/New_York" => "EST",
    "America/Chicago" => "CST",
    "America/Denver" => "MST",
    "America/Los_Angeles" => "PST",
    "America/Phoenix" => "MST",
    "Europe/London" => "GMT",
    "Europe/Paris" => "CET",
    "Europe/Berlin" => "CET",
    "Asia/Tokyo" => "JST",
    "Asia/Shanghai" => "CST",
    "Asia/Dubai" => "GST",
    "Australia/Sydney" => "AEST"
  }

  @doc """
  Formats a DateTime into a human-readable "time ago" string.

  Supports short time periods (seconds, minutes, hours, days) and
  long time periods (months, years) for things like build timestamps.

  ## Examples

      iex> datetime = DateTime.add(DateTime.utc_now(), -65, :second)
      iex> ToweropsWeb.TimeHelpers.format_time_ago(datetime)
      "1m ago"

      iex> datetime = DateTime.add(DateTime.utc_now(), -7200, :second)
      iex> ToweropsWeb.TimeHelpers.format_time_ago(datetime)
      "2h ago"

  """
  @spec format_time_ago(DateTime.t() | nil) :: String.t()
  def format_time_ago(nil), do: "Never"

  def format_time_ago(datetime) do
    now = DateTime.utc_now()
    diff = DateTime.diff(now, datetime, :second)

    cond do
      diff < 60 ->
        "#{diff}s ago"

      diff < 3600 ->
        minutes = div(diff, 60)
        "#{minutes}m ago"

      diff < 86_400 ->
        hours = div(diff, 3600)
        "#{hours}h ago"

      diff < 2_592_000 ->
        days = div(diff, 86_400)
        "#{days}d ago"

      diff < 31_536_000 ->
        months = div(diff, 2_592_000)
        "#{months}mo ago"

      true ->
        years = div(diff, 31_536_000)
        "#{years}y ago"
    end
  end

  @doc """
  Formats a DateTime into a full date and time string in UTC.
  """
  @spec format_datetime(DateTime.t() | nil) :: String.t()
  def format_datetime(datetime), do: format_datetime(datetime, "UTC")

  @doc """
  Formats a DateTime into a full date and time string, converted to the given timezone.

  ## Examples

      iex> datetime = ~U[2026-01-15 14:34:00Z]
      iex> ToweropsWeb.TimeHelpers.format_datetime(datetime, "America/New_York")
      "Jan 15, 2026 at 09:34 AM EST"

      iex> datetime = ~U[2026-01-15 14:34:00Z]
      iex> ToweropsWeb.TimeHelpers.format_datetime(datetime, "UTC")
      "Jan 15, 2026 at 02:34 PM UTC"

  """
  @spec format_datetime(DateTime.t() | nil, String.t() | nil) :: String.t()
  def format_datetime(nil, _timezone), do: "Never"

  def format_datetime(datetime, timezone) when is_binary(timezone) do
    case shift_timezone(datetime, timezone) do
      {:ok, shifted_dt, tz_abbr} ->
        Calendar.strftime(shifted_dt, "%b %d, %Y at %I:%M %p #{tz_abbr}")

      {:error, _reason} ->
        # Fallback to UTC if timezone is invalid
        Calendar.strftime(datetime, "%b %d, %Y at %I:%M %p UTC")
    end
  end

  @doc """
  Formats a DateTime into a short date string in UTC.
  """
  @spec format_date(DateTime.t() | nil) :: String.t()
  def format_date(datetime), do: format_date(datetime, "UTC")

  @doc """
  Formats a DateTime into a short date string, converted to the given timezone.

  ## Examples

      iex> datetime = ~U[2026-01-15 14:34:00Z]
      iex> ToweropsWeb.TimeHelpers.format_date(datetime, "UTC")
      "Jan 15, 2026"

      iex> datetime = ~U[2026-01-15 02:00:00Z]
      iex> ToweropsWeb.TimeHelpers.format_date(datetime, "America/Los_Angeles")
      "Jan 14, 2026"

  """
  @spec format_date(DateTime.t() | nil, String.t() | nil) :: String.t()
  def format_date(nil, _timezone), do: "Never"

  def format_date(datetime, timezone) when is_binary(timezone) do
    case shift_timezone(datetime, timezone) do
      {:ok, shifted_dt, _tz_abbr} ->
        Calendar.strftime(shifted_dt, "%b %d, %Y")

      {:error, _reason} ->
        # Fallback to UTC if timezone is invalid
        Calendar.strftime(datetime, "%b %d, %Y")
    end
  end

  @doc """
  Formats a DateTime into ISO 8601 format in UTC.
  """
  @spec format_iso8601(DateTime.t() | nil) :: String.t()
  def format_iso8601(datetime), do: format_iso8601(datetime, "UTC")

  @doc """
  Formats a DateTime into ISO 8601 format, converted to the given timezone.

  ## Examples

      iex> datetime = ~U[2026-01-15 14:34:00Z]
      iex> ToweropsWeb.TimeHelpers.format_iso8601(datetime, "UTC")
      "2026-01-15 14:34:00 UTC"

      iex> datetime = ~U[2026-01-15 14:34:00Z]
      iex> ToweropsWeb.TimeHelpers.format_iso8601(datetime, "America/New_York")
      "2026-01-15 09:34:00 EST"

  """
  @spec format_iso8601(DateTime.t() | nil, String.t() | nil) :: String.t()
  def format_iso8601(nil, _timezone), do: "Never"

  def format_iso8601(datetime, timezone) when is_binary(timezone) do
    case shift_timezone(datetime, timezone) do
      {:ok, shifted_dt, tz_abbr} ->
        Calendar.strftime(shifted_dt, "%Y-%m-%d %H:%M:%S #{tz_abbr}")

      {:error, _reason} ->
        # Fallback to UTC if timezone is invalid
        Calendar.strftime(datetime, "%Y-%m-%d %H:%M:%S UTC")
    end
  end

  # Private helper to shift a DateTime by a timezone offset
  defp shift_timezone(datetime, timezone) do
    case Map.get(@timezone_offsets, timezone) do
      nil ->
        {:error, :unknown_timezone}

      offset_seconds ->
        shifted_dt = DateTime.add(datetime, offset_seconds, :second)
        tz_abbr = Map.get(@timezone_abbrs, timezone, timezone)
        {:ok, shifted_dt, tz_abbr}
    end
  end
end
