defmodule Towerops.Agents.ReleaseChecker do
  @moduledoc """
  Fetches latest release info from GitHub Releases API for the towerops-agent repo.

  Caches results for 5 minutes to avoid excessive API calls.
  """

  require Logger

  @github_repo "towerops-app/towerops-agent"
  @cache_ttl_ms to_timeout(minute: 5)

  @arch_map %{
    "x86_64" => "amd64",
    "aarch64" => "arm64"
  }

  @doc """
  Clears the cached release info so the next call to `get_latest_release/0`
  fetches fresh data from GitHub.
  """
  @spec invalidate_cache() :: :ok
  def invalidate_cache do
    :persistent_term.erase({__MODULE__, :release})
    :ok
  rescue
    ArgumentError -> :ok
  end

  @doc """
  Returns the latest release info from GitHub.

  Returns `{:ok, %{version: String.t(), assets: [asset]}}` where each asset
  has `name`, `url`, and `checksum` fields.

  Results are cached for 5 minutes.
  """
  @spec get_latest_release() :: {:ok, map()} | {:error, term()}
  def get_latest_release do
    case get_cached() do
      {:ok, release} ->
        {:ok, release}

      :miss ->
        fetch_and_cache()
    end
  end

  @doc """
  Finds the asset matching the given architecture.

  Maps Rust architecture strings (x86_64, aarch64) to release asset names (amd64, arm64).
  """
  @spec asset_for_arch([map()], String.t()) :: {:ok, map()} | {:error, :no_matching_asset}
  def asset_for_arch(assets, arch) do
    target = Map.get(@arch_map, arch, arch)

    case Enum.find(assets, fn asset -> String.contains?(asset.name, target) end) do
      nil -> {:error, :no_matching_asset}
      asset -> {:ok, asset}
    end
  end

  defp get_cached do
    case :persistent_term.get({__MODULE__, :release}, nil) do
      {release, cached_at} ->
        if System.monotonic_time(:millisecond) - cached_at < @cache_ttl_ms do
          {:ok, release}
        else
          :miss
        end

      nil ->
        :miss
    end
  end

  defp fetch_and_cache do
    url = "https://api.github.com/repos/#{@github_repo}/releases/latest"

    case Req.get(url, headers: [{"accept", "application/vnd.github+json"}]) do
      {:ok, %Req.Response{status: 200, body: body}} ->
        release = parse_release(body)
        :persistent_term.put({__MODULE__, :release}, {release, System.monotonic_time(:millisecond)})
        {:ok, release}

      {:ok, %Req.Response{status: status}} ->
        Logger.warning("GitHub API returned status #{status} for latest release")
        {:error, {:github_api, status}}

      {:error, reason} ->
        Logger.warning("Failed to fetch latest release from GitHub: #{inspect(reason)}")
        {:error, reason}
    end
  end

  defp parse_release(body) do
    version = String.trim_leading(body["tag_name"], "v")

    assets =
      (body["assets"] || [])
      |> Enum.filter(fn asset ->
        name = asset["name"]
        String.starts_with?(name, "towerops-agent-linux-") and not String.ends_with?(name, ".sha256")
      end)
      |> Enum.map(fn asset ->
        checksum = fetch_checksum(body["assets"], asset["name"])

        %{
          name: asset["name"],
          url: asset["browser_download_url"],
          checksum: checksum
        }
      end)

    %{version: version, assets: assets}
  end

  defp fetch_checksum(all_assets, binary_name) do
    sha_asset = Enum.find(all_assets, fn a -> a["name"] == "#{binary_name}.sha256" end)

    if sha_asset do
      case Req.get(sha_asset["browser_download_url"]) do
        {:ok, %Req.Response{status: 200, body: body}} ->
          body |> String.trim() |> String.split(" ") |> List.first()

        _ ->
          nil
      end
    end
  end
end
