defmodule Tz.Updater do
  require Logger

  alias Tz.Compiler
  alias Tz.HTTP
  alias Tz.HTTP.HTTPResponse
  alias Tz.IanaDataDir
  alias Tz.PeriodsProvider

  @doc """
  Recompiles the period maps only if more recent IANA data is available.
  """
  def maybe_recompile() do
    {_, latest_tz_version} = maybe_update_tz_database_to_latest_version()

    if latest_tz_version != PeriodsProvider.iana_version() do
      if IanaDataDir.forced_iana_version() do
        raise "cannot update time zone periods as version #{IanaDataDir.forced_iana_version()} has been forced through the :iana_version config"
      end

      Logger.info("Tz is recompiling the time zone periods...")

      try do
        Code.compiler_options(ignore_module_conflict: true)
        Compiler.compile()
        Code.compiler_options(ignore_module_conflict: false)
        Logger.info("Tz compilation done")
      rescue
        e ->
          Logger.error("""
          Failed to recompile IANA time zone data to version #{latest_tz_version}, remaining on current version #{PeriodsProvider.iana_version()}.
          Error: #{Exception.format(:error, e, __STACKTRACE__)}
          """)
      end
    end
  end

  defp maybe_update_tz_database_to_latest_version() do
    latest_version_saved = IanaDataDir.latest_tzdata_version()

    case fetch_latest_iana_tz_version() do
      {:ok, latest_version} ->
        if latest_version != latest_version_saved &&
             latest_version != PeriodsProvider.iana_version() do
          Logger.info(
            "New IANA time zone data version #{latest_version} available; currently using #{latest_version_saved}."
          )

          case update_tz_database(latest_version) do
            {:ok, _dir} ->
              IanaDataDir.delete_tzdata_dir(latest_version_saved)
              {:updated, latest_version}

            :error ->
              {:error, latest_version_saved}
          end
        else
          Logger.info(
            "Already using the latest IANA time zone data version: #{PeriodsProvider.iana_version()}."
          )

          {:already_latest, latest_version}
        end

      :error ->
        {:error, latest_version_saved}
    end
  end

  @doc false
  def fetch_latest_iana_tz_version() do
    Logger.info(
      "Tz is fetching the latest IANA time zone data version at https://data.iana.org/time-zones/tzdb/version"
    )

    case HTTP.get_http_client!().request("data.iana.org", "/time-zones/tzdb/version") do
      %HTTPResponse{body: body, status_code: 200} ->
        version = body |> String.trim()
        {:ok, version}

      _ ->
        Logger.error("Tz failed to read the latest version of the IANA time zone data")
        :error
    end
  end

  @doc false
  def update_tz_database(version, dir \\ IanaDataDir.dir())
      when is_binary(version) and is_binary(dir) do
    case download_tz_database(version) do
      {:ok, content} ->
        tzdata_dir = IanaDataDir.extract_tzdata_into_dir(version, content, dir)
        Logger.info("IANA time zone data extracted into #{tzdata_dir}")
        {:ok, tzdata_dir}

      :error ->
        :error
    end
  end

  defp download_tz_database(version) do
    Logger.info(
      "Tz is downloading the IANA time zone data version #{version} at https://data.iana.org/time-zones/releases/tzdata#{version}.tar.gz"
    )

    case HTTP.get_http_client!().request(
           "data.iana.org",
           "/time-zones/releases/tzdata#{version}.tar.gz"
         ) do
      %HTTPResponse{body: body, status_code: 200} ->
        Logger.info("Tz download done")
        {:ok, body}

      _ ->
        Logger.error("Tz failed to download the latest IANA time zone data (version #{version})")
        :error
    end
  end
end
