using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using ROLAC.API.Data;
using ROLAC.API.DTOs.Push;
using ROLAC.API.Entities.Notifications;
using ROLAC.API.Services.Logging;
using ROLAC.API.Services.Notifications;

namespace ROLAC.API.Controllers;

/// <summary>
/// Self-service Web Push subscription management for the logged-in member: hand out the VAPID public
/// key, store the browser's subscription, and remove it on opt-out. Subscriptions are keyed to the
/// caller's linked Member, so an admin-only account (no MemberId) cannot subscribe.
/// </summary>
[ApiController]
[Route("api/push")]
[Authorize]
public sealed class PushSubscriptionsController : ControllerBase
{
    private readonly AppDbContext _db;
    private readonly INotificationSettingsService _settings;
    private readonly IWebPushService _webPush;
    private readonly CurrentUserAccessor _currentUser;

    public PushSubscriptionsController(
        AppDbContext db, INotificationSettingsService settings,
        IWebPushService webPush, CurrentUserAccessor currentUser)
    {
        _db = db;
        _settings = settings;
        _webPush = webPush;
        _currentUser = currentUser;
    }

    [HttpGet("vapid-public-key")]
    public IActionResult VapidPublicKey()
        => Ok(new { publicKey = _settings.GetWebPush().PublicKey });

    [HttpPost("subscriptions")]
    public async Task<IActionResult> Subscribe([FromBody] PushSubscriptionRequest request, CancellationToken ct)
    {
        var memberId = await CurrentMemberIdAsync(ct);
        if (memberId is null)
            return Conflict(new { message = "This account is not linked to a member, so it cannot receive push notifications." });

        var existing = await _db.WebPushSubscriptions
            .FirstOrDefaultAsync(subscription => subscription.Endpoint == request.Endpoint, ct);

        if (existing is null)
        {
            _db.WebPushSubscriptions.Add(new WebPushSubscription
            {
                MemberId  = memberId.Value,
                Endpoint  = request.Endpoint,
                P256dh    = request.Keys.P256dh,
                Auth      = request.Keys.Auth,
                UserAgent = request.UserAgent,
                CreatedAt = DateTime.UtcNow,
            });
        }
        else
        {
            // Same endpoint re-subscribed (e.g. keys rotated, or now a different member on this browser).
            existing.MemberId  = memberId.Value;
            existing.P256dh    = request.Keys.P256dh;
            existing.Auth      = request.Keys.Auth;
            existing.UserAgent = request.UserAgent;
        }

        await _db.SaveChangesAsync(ct);
        return NoContent();
    }

    [HttpDelete("subscriptions")]
    public async Task<IActionResult> Unsubscribe([FromBody] PushUnsubscribeRequest request, CancellationToken ct)
    {
        var subscription = await _db.WebPushSubscriptions
            .FirstOrDefaultAsync(s => s.Endpoint == request.Endpoint, ct);
        if (subscription is not null)
        {
            _db.WebPushSubscriptions.Remove(subscription);
            await _db.SaveChangesAsync(ct);
        }
        return NoContent();
    }

    // Sends a canned push to the caller's own devices so they can confirm notifications work.
    [HttpPost("test")]
    public async Task<IActionResult> SendTestToSelf(CancellationToken ct)
    {
        var memberId = await CurrentMemberIdAsync(ct);
        if (memberId is null)
            return Conflict(new { message = "This account is not linked to a member, so it cannot receive push notifications." });

        var result = await _webPush.SendToMembersAsync(
            new[] { memberId.Value },
            new WebPushPayload("River of Life", "通知測試成功！This is a test notification.", "/user-portal/account"),
            _currentUser.UserIdOrSystem, ct);
        return Ok(result);
    }

    private async Task<int?> CurrentMemberIdAsync(CancellationToken ct)
    {
        var userId = _currentUser.UserId;
        if (string.IsNullOrEmpty(userId)) return null;
        return await _db.Users.Where(user => user.Id == userId).Select(user => user.MemberId).FirstOrDefaultAsync(ct);
    }
}
