using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using DeepDrftModels.DTOs;
using Microsoft.Extensions.DependencyInjection;
using NetBlocks.Models;

namespace DeepDrftPublic.Client.Clients;

public class TrackMediaResponse : IDisposable
{
    public Stream Stream { get; }
    public long ContentLength { get; }

    /// <summary>
    /// The response media type (e.g. "audio/wav", "audio/mpeg"). Drives format-decoder
    /// selection on the JS side. Falls back to "audio/wav" when the server omits the header.
    /// </summary>
    public string ContentType { get; }

    private readonly HttpResponseMessage _response;

    public TrackMediaResponse(Stream stream, long contentLength, string contentType, HttpResponseMessage response)
    {
        Stream = stream;
        ContentLength = contentLength;
        ContentType = contentType;
        _response = response;
    }

    public void Dispose()
    {
        Stream?.Dispose();
        _response?.Dispose();
    }
}

public class TrackMediaClient
{
    private readonly HttpClient _http;

    public TrackMediaClient(IHttpClientFactory httpClientFactory)
    {
        _http = httpClientFactory.CreateClient("DeepDrft.Content");
    }

    /// <summary>
    /// Fetches the WAV stream for a track via an HTTP Range request starting at a
    /// file-absolute byte offset. <paramref name="byteOffset"/> is the position from
    /// the start of the file on disk (including the WAV header) — callers seeking into
    /// audio data must add the header size themselves. The cancellation token aborts
    /// the in-flight server connection rather than leaving the server draining bytes
    /// into a dead socket.
    /// </summary>
    public async Task<ApiResult<TrackMediaResponse>> GetTrackMedia(
        string trackId,
        long byteOffset = 0,
        CancellationToken cancellationToken = default)
    {
        try
        {
            // Same URL for every seek — only the Range header differs. byteOffset 0 is
            // not special-cased: "bytes=0-" requests the whole file from the start.
            using var request = new HttpRequestMessage(HttpMethod.Get, $"api/track/{trackId}");
            request.Headers.Range = new RangeHeaderValue(byteOffset, null);

            // Use HttpCompletionOption.ResponseHeadersRead to get stream immediately
            var response = await _http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
            response.EnsureSuccessStatusCode();

            var contentLength = response.Content.Headers.ContentLength ?? 0;
            // Default to WAV when the server omits the header — the only format shipping
            // today — so the JS factory always receives a usable media type.
            var contentType = response.Content.Headers.ContentType?.MediaType ?? "audio/wav";
            var stream = await response.Content.ReadAsStreamAsync(cancellationToken);

            // TrackMediaResponse takes ownership of both stream and response;
            // do NOT dispose response here — the caller disposes via TrackMediaResponse.Dispose().
            return ApiResult<TrackMediaResponse>.CreatePassResult(new TrackMediaResponse(stream, contentLength, contentType, response));
        }
        catch (Exception e)
        {
            return ApiResult<TrackMediaResponse>.CreateFailResult(e.Message);
        }
    }

    /// <summary>
    /// Fetches a track's stored waveform loudness profile. A 404 means no profile is stored
    /// (existing tracks predate profiling, or computation failed at upload); callers treat that
    /// as "render a flat seekbar" rather than an error, so it surfaces as a fail result with a
    /// stable message rather than throwing.
    /// </summary>
    public async Task<ApiResult<WaveformProfileDto>> GetWaveformProfileAsync(string trackId, CancellationToken cancellationToken = default)
    {
        try
        {
            var response = await _http.GetAsync($"api/track/{trackId}/waveform", cancellationToken);
            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                return ApiResult<WaveformProfileDto>.CreateFailResult("No waveform profile available");
            }

            response.EnsureSuccessStatusCode();

            var profile = await response.Content.ReadFromJsonAsync<WaveformProfileDto>();
            if (profile is null)
            {
                return ApiResult<WaveformProfileDto>.CreateFailResult("Waveform profile response was empty");
            }

            return ApiResult<WaveformProfileDto>.CreatePassResult(profile);
        }
        catch (Exception e)
        {
            return ApiResult<WaveformProfileDto>.CreateFailResult(e.Message);
        }
    }
}
