using DeepDrftModels.DTOs;
using Models.Common;
using NetBlocks.Models;

namespace DeepDrftData;

/// <summary>
/// SQL-side track service. Repository outputs entities; this service outputs DTOs via
/// TrackConverter. In-process consumers (UnifiedTrackService, CLI, DeepDrftPublic) all
/// receive DTOs.
/// </summary>
public interface ITrackService
{
    Task<ResultContainer<TrackDto?>> GetById(long id);
    Task<ResultContainer<TrackDto?>> GetByEntryKey(string entryKey);

    /// <summary>
    /// Returns a single track chosen uniformly at random, or null when the library is empty
    /// (a valid state, not a failure). Backs the public "Stream Now" instant-play feature.
    /// </summary>
    Task<ResultContainer<TrackDto?>> GetRandom(CancellationToken cancellationToken = default);
    Task<ResultContainer<List<TrackDto>>> GetAll();
    Task<ResultContainer<PagedResult<TrackDto>>> GetPaged(int pageNumber, int pageSize, string? sortColumn, bool sortDescending, TrackFilter? filter = null, CancellationToken cancellationToken = default);

    /// <summary>All releases, title-ascending, each carrying its non-deleted track count.</summary>
    Task<ResultContainer<List<ReleaseDto>>> GetReleases(CancellationToken cancellationToken = default);

    /// <summary>Distinct non-null genres with track counts, genre-ascending.</summary>
    Task<ResultContainer<List<GenreSummaryDto>>> GetDistinctGenres(CancellationToken cancellationToken = default);

    /// <summary>
    /// Aggregate figures behind the public home hero stat row: Cut track count + per-ReleaseType Cut
    /// release breakdown, Mix release count, and total Mix runtime in seconds. One read for all three cards.
    /// </summary>
    Task<ResultContainer<HomeStatsDto>> GetHomeStats(CancellationToken cancellationToken = default);

    /// <summary>
    /// Non-deleted tracks whose SQL duration is still null — the work list for the one-time duration
    /// backfill. The backfill reads each track's stored duration from the vault and writes it via
    /// <see cref="UpdateDuration"/>.
    /// </summary>
    Task<ResultContainer<List<TrackDto>>> GetTracksMissingDuration(CancellationToken cancellationToken = default);

    /// <summary>
    /// Set the SQL duration for one track. Idempotent: a track whose duration is already set is left
    /// untouched. Backs the duration backfill. Returns the number of rows updated (0 or 1).
    /// </summary>
    Task<ResultContainer<int>> UpdateDuration(long id, double durationSeconds, CancellationToken cancellationToken = default);

    /// <summary>
    /// Unconditionally overwrite the SQL duration for one track. Unlike <see cref="UpdateDuration"/>,
    /// this carries no null guard — it is for the replace-audio path where the track already has a
    /// non-null duration that must be overwritten with the new audio's value. Returns a fail Result
    /// when zero rows are affected (track removed between lookup and write).
    /// </summary>
    Task<ResultContainer<int>> SetDuration(long id, double durationSeconds, CancellationToken cancellationToken = default);

    /// <summary>
    /// Resolve the release matching <paramref name="title"/> + <paramref name="artist"/>, creating
    /// one from <paramref name="releaseData"/> when none exists. Backs the upload flow's FK
    /// resolution so a track lands on a shared release rather than duplicating release-cardinal data.
    /// The <c>WasCreated</c> flag in the result is <see langword="true"/> when a new row was inserted
    /// and <see langword="false"/> when an existing row was found (including after a lost concurrent-insert
    /// race). The CREATE path in <c>UnifiedTrackService.UploadAsync</c> uses this to turn a
    /// "found existing" outcome into a duplicate rejection rather than a silent attach.
    /// </summary>
    Task<ResultContainer<(ReleaseDto Release, bool WasCreated)>> FindOrCreateRelease(
        string title, string artist, ReleaseDto releaseData, CancellationToken cancellationToken = default);

    /// <summary>
    /// Read-only peek for an existing release by its natural key, or null when none exists — a find
    /// with no create side-effect. Backs the upload cardinality pre-check, which must read a release's
    /// medium and live-track count before deciding whether to admit an upload, without creating a
    /// release for an upload it may reject. The returned DTO carries TrackCount.
    /// </summary>
    Task<ResultContainer<ReleaseDto?>> GetReleaseByTitleAndArtist(
        string title, string artist, CancellationToken cancellationToken = default);

    Task<ResultContainer<TrackDto>> Create(TrackDto newTrack);
    Task<ResultContainer<TrackDto>> Update(TrackDto track);
    Task<Result> Delete(long id);

    /// <summary>Soft-delete a release row by id. Idempotent — a missing or already-deleted row is a no-op.</summary>
    Task<Result> DeleteRelease(long id, CancellationToken cancellationToken = default);

    /// <summary>
    /// Count of non-deleted tracks on a release. Backs the delete-cascade decision: when a track
    /// delete leaves a release with zero live tracks, the release is soft-deleted too.
    /// </summary>
    Task<ResultContainer<int>> CountLiveTracksByRelease(long releaseId, CancellationToken cancellationToken = default);
}
