using System.Text.Json;
using System.Text.Json.Serialization;
using DeepDrftModels.DTOs;
using DeepDrftModels.Enums;
using Microsoft.AspNetCore.Components;

namespace DeepDrftPublic.Client.Services;

/// <summary>
/// Records share events from <c>SharePopover</c> (Phase 16 §1b / §2.1). After a successful clipboard
/// write the popover calls <see cref="RecordShare"/>; this tracker applies the per-(target,channel)
/// debounce — at most one event per target+channel per <see cref="DebounceWindow"/> per session — and
/// fires the event via <c>navigator.sendBeacon</c> to the proxied <c>api/event/share</c> route.
///
/// <para>
/// Scoped (per-session) so the debounce memory lives for the session and resets on a fresh load, matching
/// the "feels like one act" intent: copying the same link three times in a row is one share, not three.
/// The beacon send is fire-and-forget; the current <c>anonId</c> (wave 16.3) is read synchronously from
/// the warmed <see cref="IAnonIdProvider"/> cache and omitted when null.
/// </para>
/// </summary>
public sealed class ShareTracker
{
    // One event per (target, channel) per this window per session (§1b). 60s matches the spec's
    // recommendation — long enough to fold a flurry of repeat copies into one intent.
    private static readonly TimeSpan DebounceWindow = TimeSpan.FromSeconds(60);

    // Omit a null anonId from the wire payload (§2.2). Cosmetic — the API tolerates null — and does not
    // change the integer enum encoding the 16.1 contract relies on.
    private static readonly JsonSerializerOptions BeaconJson =
        new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull };

    private readonly BeaconInterop _beacon;
    private readonly IAnonIdProvider _anonId;
    private readonly string _shareUrl;
    private readonly Dictionary<string, DateTimeOffset> _lastSent = new();

    public ShareTracker(BeaconInterop beacon, IAnonIdProvider anonId, NavigationManager navigation)
    {
        _beacon = beacon;
        _anonId = anonId;
        _shareUrl = $"{navigation.BaseUri}api/event/share";
    }

    /// <summary>
    /// Record a share unless an identical (target, channel) was recorded within the debounce window.
    /// Returns true when an event was fired, false when debounced — primarily so tests can assert the
    /// debounce without reaching into the beacon.
    /// </summary>
    public bool RecordShare(ShareTargetType targetType, string targetKey, ShareChannel channel)
        => RecordShare(targetType, targetKey, channel, DateTimeOffset.UtcNow);

    /// <summary>
    /// Debounce-aware record with an injectable <paramref name="now"/> so the 60s window is testable
    /// without wall-clock waits. The parameterless overload above passes <see cref="DateTimeOffset.UtcNow"/>.
    /// </summary>
    public bool RecordShare(ShareTargetType targetType, string targetKey, ShareChannel channel, DateTimeOffset now)
    {
        if (string.IsNullOrWhiteSpace(targetKey))
            return false;

        var dedupeKey = $"{targetType}:{targetKey}:{channel}";
        if (_lastSent.TryGetValue(dedupeKey, out var last) && now - last < DebounceWindow)
            return false;

        _lastSent[dedupeKey] = now;

        var json = JsonSerializer.Serialize(new ShareEventDto
        {
            TargetType = targetType,
            TargetKey = targetKey,
            Channel = channel,
            AnonId = _anonId.Current,
        }, BeaconJson);

        // Fire-and-forget — a dropped share telemetry event is acceptable.
        _ = _beacon.SendAsync(_shareUrl, json);
        return true;
    }
}
