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

namespace DeepDrftManager.Services;

/// <summary>
/// CMS-side track operations for the Manager host. Every read and write goes over HTTP to the
/// DeepDrftAPI API, which is the single authority over both the SQL metadata store and the
/// binary audio vault. DeepDrftManager holds no in-process data layer.
/// </summary>
public interface ICmsTrackService
{
    /// <summary>
    /// Proxy a WAV upload to DeepDrftAPI. The Content API owns the dual-database write and
    /// returns the persisted track carrying the SQL-assigned <c>Id</c>. A vault-without-SQL
    /// orphan is handled and logged server-side; here it surfaces as a failed result.
    /// <paramref name="originalFileName"/> is the browser's filename, captured at upload time and
    /// stored as metadata; it is not user-editable afterwards.
    /// <paramref name="medium"/> sets the parent release's <see cref="ReleaseMedium"/> when this upload
    /// creates the release. The medium is authoritative only on creation — adding a track to an existing
    /// release never changes its medium (that is the edit path, <see cref="UpdateAsync"/>).
    /// <paramref name="contentLength"/> is the total payload size (the browser file's <c>Size</c>); it
    /// sets Content-Length and is the denominator for <paramref name="progress"/>, which reports cumulative
    /// bytes pushed to the wire. Each progress tick also resets the idle/heartbeat upload timeout, so a
    /// stalled connection aborts without a fixed total-duration cap.
    /// <paramref name="releaseId"/> distinguishes the two rows of a within-batch multi-track Cut: null on
    /// the first row (CREATE — the server rejects a pre-existing (title, artist) as a duplicate) and the
    /// id returned by that first row on rows 2..N (ATTACH — the server skips the duplicate check and adds
    /// the track to the release the batch just created).
    /// </summary>
    Task<ResultContainer<TrackDto>> UploadTrackAsync(
        Stream wavStream,
        long contentLength,
        string fileName,
        string contentType,
        string trackName,
        string artist,
        string? album,
        string? genre,
        string? description,
        string? releaseDate,
        string? originalFileName,
        long createdByUserId,
        ReleaseType releaseType,
        int trackNumber,
        ReleaseMedium medium = ReleaseMedium.Cut,
        long? releaseId = null,
        IProgress<long>? progress = null,
        CancellationToken ct = default);

    /// <summary>
    /// Upload-form pre-flight: returns the existing release whose exact (title, artist) matches, or null
    /// when none exists. Backs the duplicate block the form runs BEFORE transferring bytes, so the admin
    /// is not surprised at the end of a long upload. A 404 from the API is the not-found (null) case, not
    /// a failure. The match semantics are the API's <c>GetReleaseByTitleAndArtist</c> — the same read the
    /// server backstop uses — so the pre-flight and the backstop agree.
    /// </summary>
    Task<ResultContainer<ReleaseDto?>> GetExistingReleaseAsync(
        string title, string artist, CancellationToken ct = default);

    /// <summary>
    /// Delete a track via the Content API, which removes the SQL row then the vault entry.
    /// Maps a 404 to a "Track not found." failure.
    /// </summary>
    Task<Result> DeleteTrackAsync(long id, CancellationToken ct = default);

    /// <summary>
    /// Replace an existing track's audio via <c>POST api/track/{id}/replace-audio</c>. Swaps only the
    /// vault bytes and regenerates the track's waveform data server-side; the track id, vault key,
    /// release membership, position, and metadata are preserved. Uses the dedicated upload client and
    /// the same two-phase (idle / response-wait) cancellation as <see cref="UploadTrackAsync"/>, since
    /// a WAV replace is a large-body upload. <paramref name="contentLength"/> sets Content-Length and is
    /// the denominator for <paramref name="progress"/>; each progress tick resets the idle heartbeat.
    /// Maps a 404 to a "Track not found." failure.
    /// </summary>
    Task<Result> ReplaceTrackAudioAsync(
        long id,
        Stream wavStream,
        long contentLength,
        string fileName,
        string contentType,
        IProgress<long>? progress = null,
        CancellationToken ct = default);

    /// <summary>
    /// Soft-delete a release record via DELETE api/track/release/{id}. Use when a release
    /// has no live tracks and needs to be removed from the albums browser.
    /// </summary>
    Task<Result> DeleteReleaseAsync(long releaseId, CancellationToken ct = default);

    /// <summary>
    /// Fetch a page of track metadata from the Content API's <c>GET api/track/page</c>. Optional
    /// <paramref name="album"/> and <paramref name="genre"/> filters narrow the result to a single
    /// release title or genre; null leaves the dimension unfiltered.
    /// </summary>
    Task<ResultContainer<PagedResult<TrackDto>>> GetPagedAsync(
        int page, int pageSize, string? sortColumn, bool sortDescending,
        string? album = null, string? genre = null,
        CancellationToken ct = default);

    /// <summary>
    /// Fetch a single track's metadata from <c>GET api/track/meta/{id}</c>. A 404 returns a
    /// passing result with a null value.
    /// </summary>
    Task<ResultContainer<TrackDto?>> GetByIdAsync(long id, CancellationToken ct = default);

    /// <summary>
    /// Upload a cover-art image to the images vault via <c>POST api/image/upload</c>.
    /// Returns the generated entry key on success. Maps a 400 to a validation failure message.
    /// </summary>
    Task<ResultContainer<string>> UploadImageAsync(
        Stream imageStream,
        string fileName,
        string contentType,
        CancellationToken ct = default);

    /// <summary>
    /// Update a track's metadata via <c>PUT api/track/meta/{id}</c>. EntryKey is immutable and
    /// not part of the update. <paramref name="imagePath"/> is tri-state: <c>null</c> leaves the
    /// cover art unchanged, <c>""</c> clears it, and any other value sets it. <paramref name="medium"/>
    /// is null = no change; a non-null, non-Cut value resets the release's ReleaseType to its default
    /// server-side, since ReleaseType is meaningful only for Cut.
    /// </summary>
    Task<Result> UpdateAsync(
        long id, string trackName, string artist,
        string? album, string? genre, string? description, DateOnly? releaseDate,
        string? imagePath = null,
        ReleaseType? releaseType = null,
        ReleaseMedium? medium = null,
        int? trackNumber = null,
        CancellationToken ct = default);

    /// <summary>
    /// Fetch per-track waveform profile status from <c>GET api/track/waveform-status</c> for the
    /// CMS PreProcessing panel. Unpaged — the admin catalogue is small.
    /// </summary>
    Task<ResultContainer<WaveformStatusDto[]>> GetWaveformStatusAsync(CancellationToken ct = default);

    /// <summary>
    /// Trigger waveform profile generation for a single track via
    /// <c>POST api/track/{entryKey}/waveform</c>. Maps a 404 to a "Track audio not found." failure.
    /// </summary>
    Task<Result> GenerateWaveformProfileAsync(string entryKey, CancellationToken ct = default);

    /// <summary>
    /// Trigger high-res visualizer datum generation for a single track via
    /// <c>POST api/track/{entryKey}/waveform/high-res</c> (phase-12 §5). Re-runnable — recomputes on each
    /// call. Drives the per-row generate action and the batch backfill. Maps a 404 to a "Track audio not
    /// found." failure.
    /// </summary>
    Task<Result> GenerateHighResWaveformAsync(string entryKey, CancellationToken ct = default);

    /// <summary>Returns all releases with track counts from GET api/track/albums.</summary>
    Task<ResultContainer<List<ReleaseDto>>> GetReleasesAsync(CancellationToken ct = default);

    /// <summary>
    /// Returns the total track count by calling GET api/track/page with pageSize=1 and reading TotalCount.
    /// </summary>
    Task<ResultContainer<int>> GetTrackCountAsync(CancellationToken ct = default);
}
