using System.Text;
using Microsoft.AspNetCore.Mvc;

namespace DeepDrftPublic.Controllers;

/// <summary>
/// Proxies the anonymous telemetry write endpoints (<c>POST api/event/play</c> / <c>api/event/share</c>)
/// to DeepDrftAPI so the WASM client never makes a cross-origin request (Phase 16 §2.2). Mirrors
/// <see cref="TrackProxyController"/>'s idiom — the named <c>"DeepDrft.API"</c> client forwards the
/// request upstream — but for a POST write: the small JSON body is buffered and relayed verbatim, and
/// the upstream status (202 on success, 4xx on a rejected payload, 429 on rate limit) passes back so the
/// beacon's fire-and-forget contract is preserved end to end. SSR never posts these — they originate
/// from the browser player/share surfaces only.
/// </summary>
// A sendBeacon POST cannot attach a Blazor antiforgery token, so the telemetry write routes opt out
// explicitly. They are anonymous, idempotent-enough fire-and-forget logging — there is no
// state-changing user action to protect with CSRF tokens, and the upstream rate-limits by IP.
[ApiController]
[Route("api/event")]
[IgnoreAntiforgeryToken]
public class EventProxyController : ControllerBase
{
    private readonly HttpClient _upstream;
    private readonly ILogger<EventProxyController> _logger;

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

    /// <summary>Proxies a play event upstream. Body is opaque JSON — validated by DeepDrftAPI, not here.</summary>
    [HttpPost("play")]
    public Task<ActionResult> ForwardPlay(CancellationToken ct = default) => Forward("api/event/play", ct);

    /// <summary>Proxies a share event upstream.</summary>
    [HttpPost("share")]
    public Task<ActionResult> ForwardShare(CancellationToken ct = default) => Forward("api/event/share", ct);

    private async Task<ActionResult> Forward(string upstreamPath, CancellationToken ct)
    {
        // Buffer the small JSON body and relay it verbatim. Reading the raw body keeps the proxy
        // transparent — it does not deserialize or re-shape the payload, just forwards it.
        string body;
        using (var reader = new StreamReader(Request.Body, Encoding.UTF8))
        {
            body = await reader.ReadToEndAsync(ct);
        }

        using var request = new HttpRequestMessage(HttpMethod.Post, upstreamPath)
        {
            Content = new StringContent(body, Encoding.UTF8, "application/json")
        };

        // Forward the real client IP so DeepDrftAPI's per-IP rate limiter (Program.cs "events" policy)
        // partitions on individual listeners rather than the proxy host. Standard XFF chaining: relay
        // any inbound X-Forwarded-For from an upstream proxy (nginx), then append the connection IP
        // of the current hop (the browser → public host connection). DeepDrftAPI calls
        // UseForwardedHeaders() in production, which resolves the leftmost untrusted value in the
        // chain into Connection.RemoteIpAddress — which the rate limiter then keys on.
        var clientIp = HttpContext.Connection.RemoteIpAddress?.ToString();
        if (clientIp is not null)
        {
            var existing = Request.Headers["X-Forwarded-For"].ToString();
            var xff = string.IsNullOrEmpty(existing) ? clientIp : $"{existing}, {clientIp}";
            request.Headers.TryAddWithoutValidation("X-Forwarded-For", xff);
        }

        HttpResponseMessage upstream;
        try
        {
            upstream = await _upstream.SendAsync(request, ct);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Upstream call to DeepDrftAPI {Path} failed", upstreamPath);
            return StatusCode(502, "Upstream unavailable");
        }

        // Relay the upstream status as-is. Telemetry is fire-and-forget; the beacon never reads the
        // body, so there is nothing to relay beyond the code (202 / 400 / 429 / 5xx).
        using (upstream)
        {
            return StatusCode((int)upstream.StatusCode);
        }
    }
}
