using Microsoft.AspNetCore.Mvc;

namespace DeepDrftPublic.Controllers;

/// <summary>
/// Proxies public track API calls to DeepDrftAPI so the browser never makes
/// cross-origin requests. The WASM client points both named HttpClients at
/// this host; this controller forwards unauthenticated public routes upstream.
/// SSR prerender calls DeepDrftAPI directly (server-to-server) via the same
/// named clients — no proxy hop needed on the server side.
/// </summary>
[ApiController]
[Route("api/track")]
public class TrackProxyController : ControllerBase
{
    private readonly HttpClient _upstream;
    private readonly ILogger<TrackProxyController> _logger;

    public TrackProxyController(IHttpClientFactory httpClientFactory, ILogger<TrackProxyController> logger)
    {
        _upstream = httpClientFactory.CreateClient("DeepDrft.API");
        _logger = logger;
    }

    /// <summary>Proxies paged track metadata from DeepDrftAPI, forwarding optional filter params.</summary>
    [HttpGet("page")]
    public async Task<ActionResult> GetPage(
        [FromQuery] int page = 1,
        [FromQuery] int pageSize = 20,
        [FromQuery] string? sortColumn = null,
        [FromQuery] bool sortDescending = false,
        [FromQuery] string? q = null,
        [FromQuery] string? album = null,
        [FromQuery] string? genre = null,
        [FromQuery] long? releaseId = null,
        CancellationToken ct = default)
    {
        var query = $"api/track/page?page={page}&pageSize={pageSize}&sortDescending={sortDescending}";
        if (!string.IsNullOrWhiteSpace(sortColumn))
            query += $"&sortColumn={Uri.EscapeDataString(sortColumn)}";
        if (!string.IsNullOrWhiteSpace(q))
            query += $"&q={Uri.EscapeDataString(q)}";
        if (!string.IsNullOrWhiteSpace(album))
            query += $"&album={Uri.EscapeDataString(album)}";
        if (!string.IsNullOrWhiteSpace(genre))
            query += $"&genre={Uri.EscapeDataString(genre)}";
        if (releaseId is { } rid)
            query += $"&releaseId={rid}";

        HttpResponseMessage upstream;
        try
        {
            upstream = await _upstream.GetAsync(query, HttpCompletionOption.ResponseHeadersRead, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Upstream call to DeepDrftAPI track/page failed");
            return StatusCode(502, "Upstream unavailable");
        }

        using (upstream)
        {
            if (!upstream.IsSuccessStatusCode)
            {
                _logger.LogWarning("DeepDrftAPI track/page returned {Status}", (int)upstream.StatusCode);
                return StatusCode((int)upstream.StatusCode);
            }

            var json = await upstream.Content.ReadAsStringAsync(ct);
            return Content(json, "application/json");
        }
    }

    /// <summary>
    /// Proxies the random-track metadata lookup from DeepDrftAPI. Unauthenticated, same posture as
    /// the paged listing. Small JSON, buffered and relayed; a 404 from upstream (empty library)
    /// passes through so the client renders it as a valid empty state. Declared before the
    /// parameterized "{trackId}" route so the literal segment is never treated as a trackId.
    /// </summary>
    [HttpGet("random")]
    public async Task<ActionResult> GetRandom(CancellationToken ct = default)
    {
        HttpResponseMessage upstream;
        try
        {
            upstream = await _upstream.GetAsync("api/track/random", HttpCompletionOption.ResponseHeadersRead, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Upstream call to DeepDrftAPI track/random failed");
            return StatusCode(502, "Upstream unavailable");
        }

        using (upstream)
        {
            if (!upstream.IsSuccessStatusCode)
            {
                _logger.LogWarning("DeepDrftAPI track/random returned {Status}", (int)upstream.StatusCode);
                return StatusCode((int)upstream.StatusCode);
            }

            var json = await upstream.Content.ReadAsStringAsync(ct);
            return Content(json, "application/json");
        }
    }

    /// <summary>
    /// Proxies the distinct-albums browse list from DeepDrftAPI. Unauthenticated, same posture as
    /// the paged listing. Small JSON, buffered and relayed. Literal segment, declared before the
    /// parameterized "{trackId}" route so it is never treated as a trackId.
    /// </summary>
    [HttpGet("albums")]
    public async Task<ActionResult> GetAlbums(CancellationToken ct = default)
    {
        HttpResponseMessage upstream;
        try
        {
            upstream = await _upstream.GetAsync("api/track/albums", HttpCompletionOption.ResponseHeadersRead, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Upstream call to DeepDrftAPI track/albums failed");
            return StatusCode(502, "Upstream unavailable");
        }

        using (upstream)
        {
            if (!upstream.IsSuccessStatusCode)
            {
                _logger.LogWarning("DeepDrftAPI track/albums returned {Status}", (int)upstream.StatusCode);
                return StatusCode((int)upstream.StatusCode);
            }

            var json = await upstream.Content.ReadAsStringAsync(ct);
            return Content(json, "application/json");
        }
    }

    /// <summary>
    /// Proxies the distinct-genres browse list from DeepDrftAPI. Unauthenticated, same posture as
    /// the paged listing. Small JSON, buffered and relayed. Literal segment, declared before the
    /// parameterized "{trackId}" route so it is never treated as a trackId.
    /// </summary>
    [HttpGet("genres")]
    public async Task<ActionResult> GetGenres(CancellationToken ct = default)
    {
        HttpResponseMessage upstream;
        try
        {
            upstream = await _upstream.GetAsync("api/track/genres", HttpCompletionOption.ResponseHeadersRead, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Upstream call to DeepDrftAPI track/genres failed");
            return StatusCode(502, "Upstream unavailable");
        }

        using (upstream)
        {
            if (!upstream.IsSuccessStatusCode)
            {
                _logger.LogWarning("DeepDrftAPI track/genres returned {Status}", (int)upstream.StatusCode);
                return StatusCode((int)upstream.StatusCode);
            }

            var json = await upstream.Content.ReadAsStringAsync(ct);
            return Content(json, "application/json");
        }
    }

    /// <summary>
    /// Proxies single-track metadata lookup by vault entry key from DeepDrftAPI. Unauthenticated,
    /// same posture as the paged listing. Small JSON, so it is buffered and relayed; a 404 from
    /// upstream (no track with that entry key) passes through. Declared before the parameterized
    /// "{trackId}" route, though the 3-segment template makes a collision impossible regardless.
    /// </summary>
    [HttpGet("meta/by-key/{entryKey}")]
    public async Task<ActionResult> GetMetaByKey(string entryKey, CancellationToken ct = default)
    {
        var path = $"api/track/meta/by-key/{Uri.EscapeDataString(entryKey)}";

        HttpResponseMessage upstream;
        try
        {
            upstream = await _upstream.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Upstream call to DeepDrftAPI track/meta/by-key/{EntryKey} failed", entryKey);
            return StatusCode(502, "Upstream unavailable");
        }

        using (upstream)
        {
            if (!upstream.IsSuccessStatusCode)
            {
                _logger.LogWarning("DeepDrftAPI track/meta/by-key/{EntryKey} returned {Status}", entryKey, (int)upstream.StatusCode);
                return StatusCode((int)upstream.StatusCode);
            }

            var json = await upstream.Content.ReadAsStringAsync(ct);
            return Content(json, "application/json");
        }
    }

    /// <summary>
    /// Proxies audio streaming from DeepDrftAPI as a transparent HTTP Range relay.
    /// Forwards the incoming Range header upstream and relays the upstream status
    /// (200 full, 206 partial, 416 unsatisfiable) and range-related response headers
    /// back to the browser verbatim. The proxy does not slice — the upstream already did.
    /// </summary>
    [HttpGet("{trackId}")]
    public async Task<ActionResult> GetTrack(
        string trackId,
        CancellationToken ct = default)
    {
        var rangeHeader = Request.Headers.Range.ToString();
        _logger.LogInformation("Proxying track {TrackId} range '{Range}'", trackId, rangeHeader);

        var request = new HttpRequestMessage(
            HttpMethod.Get,
            $"api/track/{Uri.EscapeDataString(trackId)}");

        // Forward the browser's Range header upstream so DeepDrftAPI slices the file.
        // TryAddWithoutValidation avoids RangeHeaderValue reparsing — we relay the raw
        // header verbatim, keeping the proxy transparent.
        if (!string.IsNullOrEmpty(rangeHeader))
            request.Headers.TryAddWithoutValidation("Range", rangeHeader);

        HttpResponseMessage upstream;
        try
        {
            upstream = await _upstream.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Upstream call to DeepDrftAPI track/{TrackId} failed", trackId);
            return StatusCode(502, "Upstream unavailable");
        }

        // 416 Range Not Satisfiable is a legitimate upstream answer (seek past EOF);
        // relay it as-is rather than collapsing it into a 502.
        if ((int)upstream.StatusCode == StatusCodes.Status416RangeNotSatisfiable)
        {
            upstream.Dispose();
            return StatusCode(StatusCodes.Status416RangeNotSatisfiable);
        }

        // 206 Partial Content reports IsSuccessStatusCode == true, so this guard only
        // catches genuine upstream failures (404, 5xx).
        if (!upstream.IsSuccessStatusCode)
        {
            upstream.Dispose();
            _logger.LogWarning("DeepDrftAPI track/{TrackId} returned {Status}", trackId, (int)upstream.StatusCode);
            return StatusCode((int)upstream.StatusCode);
        }

        // Stream the body manually rather than via File(): FileStreamResult forces the
        // status to 200 (with enableRangeProcessing:false) and would clobber a relayed
        // 206. Writing status + headers + body directly keeps the partial-content
        // contract intact, with the proxy doing zero slicing of its own.
        HttpContext.Response.RegisterForDispose(upstream);
        Response.StatusCode = (int)upstream.StatusCode;
        Response.ContentType = upstream.Content.Headers.ContentType?.ToString() ?? "audio/wav";

        // Relay range-related headers so the browser sees the same partial-content
        // contract the upstream emitted.
        if (upstream.Headers.AcceptRanges.Count > 0)
            Response.Headers.AcceptRanges = string.Join(", ", upstream.Headers.AcceptRanges);
        if (upstream.Content.Headers.ContentRange is { } contentRange)
            Response.Headers.ContentRange = contentRange.ToString();
        if (upstream.Content.Headers.ContentLength is { } contentLength)
            Response.ContentLength = contentLength;

        var stream = await upstream.Content.ReadAsStreamAsync(ct);
        try
        {
            await stream.CopyToAsync(Response.Body, ct);
            return new EmptyResult();
        }
        catch (OperationCanceledException)
        {
            // Client navigated away or issued a new seek — nothing to do.
            // The upstream connection is cleaned up via RegisterForDispose.
            return new EmptyResult();
        }
    }

    /// <summary>
    /// Proxies a track's stored waveform profile (JSON) from DeepDrftAPI. Unauthenticated,
    /// same posture as the audio stream forward. The profile is small JSON, so it is buffered
    /// and relayed rather than streamed; a 404 from upstream (no profile stored) passes through.
    /// </summary>
    [HttpGet("{trackId}/waveform")]
    public async Task<ActionResult> GetWaveform(string trackId, CancellationToken ct = default)
    {
        var path = $"api/track/{Uri.EscapeDataString(trackId)}/waveform";

        HttpResponseMessage upstream;
        try
        {
            upstream = await _upstream.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Upstream call to DeepDrftAPI track/{TrackId}/waveform failed", trackId);
            return StatusCode(502, "Upstream unavailable");
        }

        using (upstream)
        {
            if (!upstream.IsSuccessStatusCode)
            {
                _logger.LogWarning("DeepDrftAPI track/{TrackId}/waveform returned {Status}", trackId, (int)upstream.StatusCode);
                return StatusCode((int)upstream.StatusCode);
            }

            var json = await upstream.Content.ReadAsStringAsync(ct);
            return Content(json, "application/json");
        }
    }

    /// <summary>
    /// Proxies a track's high-res waveform datum (JSON) from DeepDrftAPI — the per-track datum the lava
    /// visualizer fetches for the current track (phase-12 §5b). Unauthenticated, same posture as the
    /// 512-bucket profile forward above; the "high-res" suffix selects the TrackWaveforms datum. Small
    /// JSON, buffered and relayed; a 404 (no high-res datum stored — track not yet backfilled) passes
    /// through so the visualizer blanks gracefully.
    /// </summary>
    [HttpGet("{trackId}/waveform/high-res")]
    public async Task<ActionResult> GetHighResWaveform(string trackId, CancellationToken ct = default)
    {
        var path = $"api/track/{Uri.EscapeDataString(trackId)}/waveform/high-res";

        HttpResponseMessage upstream;
        try
        {
            upstream = await _upstream.GetAsync(path, HttpCompletionOption.ResponseHeadersRead, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Upstream call to DeepDrftAPI track/{TrackId}/waveform/high-res failed", trackId);
            return StatusCode(502, "Upstream unavailable");
        }

        using (upstream)
        {
            if (!upstream.IsSuccessStatusCode)
            {
                _logger.LogWarning("DeepDrftAPI track/{TrackId}/waveform/high-res returned {Status}", trackId, (int)upstream.StatusCode);
                return StatusCode((int)upstream.StatusCode);
            }

            var json = await upstream.Content.ReadAsStringAsync(ct);
            return Content(json, "application/json");
        }
    }
}
