using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data;
using ROLAC.API.Entities.Notifications;
using ROLAC.API.Services.Logging;

namespace ROLAC.API.Services.Notifications;

/// <summary>
/// Resolves the target members' device subscriptions, pushes one notification to each via the
/// <see cref="IWebPushSender"/>, prunes any subscription the push service reports as gone (404/410),
/// and writes a NotificationLog row per send. A single failure never aborts the batch — it is
/// recorded and reported in the summary (mirrors <see cref="EmailService"/>).
/// </summary>
public sealed class WebPushService : IWebPushService
{
    // The endpoint URL can be long; NotificationLog.TargetExternalId is capped at 200.
    private const int TargetExternalIdMaxLength = 200;

    private readonly AppDbContext _db;
    private readonly IWebPushSender _sender;
    private readonly CurrentUserAccessor _currentUser;

    public WebPushService(AppDbContext db, IWebPushSender sender, CurrentUserAccessor currentUser)
    {
        _db = db;
        _sender = sender;
        _currentUser = currentUser;
    }

    public async Task<NotificationResult> SendToMembersAsync(
        IReadOnlyCollection<int> memberIds, WebPushPayload payload,
        string sentByUserId, CancellationToken ct = default)
    {
        if (memberIds.Count == 0) return NotificationResult.Empty;

        var subscriptions = await _db.WebPushSubscriptions
            .Where(subscription => memberIds.Contains(subscription.MemberId))
            .ToListAsync(ct);
        if (subscriptions.Count == 0) return NotificationResult.Empty;

        var sentBy = string.IsNullOrEmpty(sentByUserId) ? _currentUser.UserIdOrSystem : sentByUserId;
        var payloadJson = BuildPayloadJson(payload);
        var failures = new List<NotificationFailure>();
        var expired = new List<WebPushSubscription>();
        var sentCount = 0;

        foreach (var subscription in subscriptions)
        {
            var log = new NotificationLog
            {
                Channel          = NotificationChannels.WebPush,
                TargetType       = NotificationTargetTypes.User,
                TargetExternalId = TruncateEndpoint(subscription.Endpoint),
                Subject          = payload.Title,
                MemberId         = subscription.MemberId,
                Body             = NotificationLogText.Truncate(payload.Body),
                SentByUserId     = sentBy,
                SentAt           = DateTime.UtcNow,
            };

            var result = await _sender.SendAsync(
                new WebPushTarget(subscription.Endpoint, subscription.P256dh, subscription.Auth), payloadJson, ct);

            if (result.Success)
            {
                log.Status = NotificationStatuses.Sent;
                subscription.LastUsedAt = DateTime.UtcNow;
                sentCount++;
            }
            else
            {
                log.Status = NotificationStatuses.Failed;
                log.Error  = result.Error;
                failures.Add(new NotificationFailure(log.TargetExternalId, result.Error ?? "unknown error"));
                if (result.IsExpired) expired.Add(subscription);
            }

            _db.NotificationLogs.Add(log);
        }

        // Drop subscriptions the push service says are gone so they don't linger and fail forever.
        if (expired.Count > 0) _db.WebPushSubscriptions.RemoveRange(expired);

        await _db.SaveChangesAsync(ct);
        return new NotificationResult(sentCount, failures.Count, failures);
    }

    // Shape expected by @angular/service-worker (ngsw): a top-level "notification" object whose
    // "data.url" the app reads on click to navigate. "tag" lets a newer message replace an older one.
    private static string BuildPayloadJson(WebPushPayload payload)
    {
        var notification = new Dictionary<string, object?>
        {
            ["title"] = payload.Title,
            ["body"]  = payload.Body,
            ["icon"]  = "assets/AppLogo-192.png",
            ["data"]  = new Dictionary<string, object?>
            {
                ["url"] = payload.Url,
                ["tag"] = payload.Tag,
            },
        };
        if (!string.IsNullOrWhiteSpace(payload.Tag)) notification["tag"] = payload.Tag;

        return JsonSerializer.Serialize(new Dictionary<string, object?> { ["notification"] = notification });
    }

    private static string TruncateEndpoint(string endpoint) =>
        endpoint.Length <= TargetExternalIdMaxLength ? endpoint : endpoint[..TargetExternalIdMaxLength];
}
