defmodule MusicLibrary.Maintenance do
  @moduledoc """
  Context for database maintenance operations, background job monitoring,
  and scrobble data quality diagnostics.
  """

  import Ecto.Query

  alias LastFm.Track
  alias MusicLibrary.BackgroundRepo
  alias MusicLibrary.Repo

  @doc """
  Counts active Oban jobs for the given worker module name.

  Active jobs are those in "available", "scheduled", "executing", or "retryable" states.
  """
  @spec count_active_jobs(String.t()) :: non_neg_integer() | nil
  def count_active_jobs(worker) do
    query =
      from j in Oban.Job,
        where: j.worker == ^worker,
        where: j.state in ["available", "scheduled", "executing", "retryable"],
        select: count(j.id)

    BackgroundRepo.one(query)
  end

  @doc """
  Runs VACUUM on the main database.
  """
  @spec vacuum() :: :ok
  def vacuum do
    Repo.vacuum()
  end

  @doc """
  Runs PRAGMA optimize on the main database.
  """
  @spec optimize() :: :ok
  def optimize do
    Repo.optimize()
  end

  # Scrobble data quality diagnostics

  @spec count_tracks_missing_artist_musicbrainz_id() :: non_neg_integer()
  def count_tracks_missing_artist_musicbrainz_id do
    query =
      from t in Track,
        where:
          fragment("json_extract(?, '$.musicbrainz_id') IS NULL", t.artist) or
            fragment("json_extract(?, '$.musicbrainz_id') = ''", t.artist),
        select: count(t.scrobbled_at_uts)

    Repo.one(query) || 0
  end

  @spec count_tracks_missing_album_musicbrainz_id() :: non_neg_integer()
  def count_tracks_missing_album_musicbrainz_id do
    query =
      from t in Track,
        where:
          fragment("json_extract(?, '$.musicbrainz_id') IS NULL", t.album) or
            fragment("json_extract(?, '$.musicbrainz_id') = ''", t.album),
        select: count(t.scrobbled_at_uts)

    Repo.one(query) || 0
  end

  @doc """
  Gets artists with missing MusicBrainz IDs, grouped by artist name.

  Returns a list of maps with artist name and track count.
  """
  @spec get_artists_missing_musicbrainz_id(keyword()) :: [map()]
  def get_artists_missing_musicbrainz_id(opts \\ []) do
    limit = Keyword.get(opts, :limit)

    query =
      from t in Track,
        where:
          fragment("json_extract(?, '$.musicbrainz_id') IS NULL", t.artist) or
            fragment("json_extract(?, '$.musicbrainz_id') = ''", t.artist),
        select: %{
          artist_name: fragment("json_extract(?, '$.name')", t.artist),
          track_count: count(t.scrobbled_at_uts)
        },
        group_by: fragment("json_extract(?, '$.name')", t.artist),
        order_by: [desc: count(t.scrobbled_at_uts)]

    query =
      if limit do
        from q in query, limit: ^limit
      else
        query
      end

    Repo.all(query)
  end

  @doc """
  Gets albums with missing MusicBrainz IDs, grouped by album title and artist.

  Returns a list of maps with album title, artist name, and track count.
  """
  @spec get_albums_missing_musicbrainz_id(keyword()) :: [map()]
  def get_albums_missing_musicbrainz_id(opts \\ []) do
    limit = Keyword.get(opts, :limit)

    query =
      from t in Track,
        where:
          fragment("json_extract(?, '$.musicbrainz_id') IS NULL", t.album) or
            fragment("json_extract(?, '$.musicbrainz_id') = ''", t.album),
        select: %{
          album_title: fragment("json_extract(?, '$.title')", t.album),
          artist_name: fragment("json_extract(?, '$.name')", t.artist),
          track_count: count(t.scrobbled_at_uts)
        },
        group_by: [
          fragment("json_extract(?, '$.title')", t.album),
          fragment("json_extract(?, '$.name')", t.artist)
        ],
        order_by: [desc: count(t.scrobbled_at_uts)]

    query =
      if limit do
        from q in query, limit: ^limit
      else
        query
      end

    Repo.all(query)
  end
end
