using System.Net.Http;
using System.Text;
using System.Text.Json;
using Metin2Launcher.Logging;

namespace Metin2Launcher.Telemetry;

/// <summary>
/// Opt-in "client applied this release" ping. Fires at most once per update
/// run, after the apply phase succeeds, and only if a non-empty template URL
/// is configured on the launcher settings. The caller passes in an
/// <see cref="HttpClient"/> that the reporter does NOT own.
///
/// Design rules:
///   - timeout is strictly bounded (5 seconds) — telemetry never blocks the
///     launch flow
///   - failures are always swallowed and logged as warnings
///   - nothing personally identifying is sent: just format, version and a
///     best-effort machine identifier derived from the env
///   - no retries, no queueing, no persistent buffer
/// </summary>
public sealed class ClientAppliedReporter
{
    public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(5);

    private readonly HttpClient _http;
    private readonly string? _urlTemplate;

    public ClientAppliedReporter(HttpClient http, string? urlTemplate)
    {
        _http = http;
        _urlTemplate = urlTemplate;
    }

    /// <summary>True when the reporter is actually wired to a destination.</summary>
    public bool IsEnabled => !string.IsNullOrWhiteSpace(_urlTemplate);

    /// <summary>
    /// Sends a single POST to the configured URL. Returns <c>true</c> if the
    /// server accepted it (2xx), <c>false</c> on any other outcome. Never throws.
    /// </summary>
    public async Task<bool> ReportAsync(string format, string version, CancellationToken ct)
    {
        if (!IsEnabled)
            return false;

        try
        {
            var url = Expand(_urlTemplate!, format, version);
            var body = JsonSerializer.SerializeToUtf8Bytes(new Payload
            {
                Format = format,
                Version = version,
                Timestamp = DateTime.UtcNow.ToString("o"),
            });

            using var linked = CancellationTokenSource.CreateLinkedTokenSource(ct);
            linked.CancelAfter(Timeout);

            using var req = new HttpRequestMessage(HttpMethod.Post, url)
            {
                Content = new ByteArrayContent(body),
            };
            req.Content.Headers.ContentType =
                new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");

            using var resp = await _http.SendAsync(req, linked.Token).ConfigureAwait(false);
            if (!resp.IsSuccessStatusCode)
            {
                Log.Warn($"telemetry: client-applied POST returned {(int)resp.StatusCode}");
                return false;
            }
            Log.Info($"telemetry: reported client-applied format={format} version={version}");
            return true;
        }
        catch (OperationCanceledException) when (!ct.IsCancellationRequested)
        {
            Log.Warn($"telemetry: client-applied POST timed out after {Timeout.TotalSeconds}s");
            return false;
        }
        catch (Exception ex)
        {
            Log.Warn("telemetry: client-applied POST failed: " + ex.Message);
            return false;
        }
    }

    /// <summary>
    /// Expands <c>{format}</c> and <c>{version}</c> placeholders in the URL
    /// template. Case-sensitive, by design — we want obvious failures if the
    /// template is wrong.
    /// </summary>
    public static string Expand(string template, string format, string version)
    {
        var sb = new StringBuilder(template);
        sb.Replace("{format}", Uri.EscapeDataString(format));
        sb.Replace("{version}", Uri.EscapeDataString(version));
        return sb.ToString();
    }

    private sealed class Payload
    {
        public string Format { get; set; } = "";
        public string Version { get; set; } = "";
        public string Timestamp { get; set; } = "";
    }
}
