using Microsoft.Extensions.Logging;
using Microsoft.EntityFrameworkCore;
using UniVerse.Application.DTOs.Common;
using UniVerse.Application.DTOs.Notifications;
using UniVerse.Application.Interfaces;
using UniVerse.Domain.Entities;
using UniVerse.Infrastructure.Data;

namespace UniVerse.Infrastructure.Notifications;

public class NotificationService : INotificationService
{
    private readonly AppDbContext _db;
    private readonly IEnumerable<INotificationProvider> _providers;
    private readonly INotificationScheduler _scheduler;
    private readonly ILogger<NotificationService> _logger;

    public NotificationService(
        AppDbContext db,
        IEnumerable<INotificationProvider> providers,
        INotificationScheduler scheduler,
        ILogger<NotificationService> logger)
    {
        _db = db;
        _providers = providers;
        _scheduler = scheduler;
        _logger = logger;
    }

    public async Task SendAsync(NotificationMessage message, CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(message.Channel);
        ArgumentException.ThrowIfNullOrWhiteSpace(message.Recipient);
        ArgumentException.ThrowIfNullOrWhiteSpace(message.Subject);
        ArgumentException.ThrowIfNullOrWhiteSpace(message.Body);

        var provider = _providers.FirstOrDefault(p => string.Equals(p.Channel, message.Channel, StringComparison.OrdinalIgnoreCase))
            ?? throw new InvalidOperationException($"Notification provider for channel '{message.Channel}' is not registered.");

        _logger.LogInformation("Dispatching notification through {Channel} to {Recipient}", message.Channel, message.Recipient);
        await provider.SendAsync(message, cancellationToken);
    }

    public Task<ScheduledNotificationResponse> ScheduleAsync(ScheduleNotificationRequest request, CancellationToken cancellationToken = default)
    {
        var message = new NotificationMessage(
            request.Channel,
            request.Recipient,
            request.Subject,
            request.Body,
            request.RecipientName,
            request.Metadata);

        return _scheduler.ScheduleAsync(message, request.SendAt, cancellationToken: cancellationToken);
    }

    public async Task<UserNotificationDto> CreateUserNotificationAsync(
        int userId,
        string type,
        string title,
        string body,
        CancellationToken cancellationToken = default)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(type);
        ArgumentException.ThrowIfNullOrWhiteSpace(title);
        ArgumentException.ThrowIfNullOrWhiteSpace(body);

        var notification = new UserNotification
        {
            UserId = userId,
            Type = type,
            Title = title,
            Body = body
        };

        _db.UserNotifications.Add(notification);
        await _db.SaveChangesAsync(cancellationToken);

        return ToDto(notification);
    }

    public async Task<PagedResult<UserNotificationDto>> GetUserNotificationsAsync(
        int userId,
        PaginationRequest pagination,
        CancellationToken cancellationToken = default)
    {
        var query = _db.UserNotifications.Where(n => n.UserId == userId);
        var total = await query.CountAsync(cancellationToken);
        var items = await query
            .OrderByDescending(n => n.CreatedAt)
            .Skip((pagination.Page - 1) * pagination.PageSize)
            .Take(pagination.PageSize)
            .Select(n => new UserNotificationDto(
                n.Id,
                n.Type,
                n.Title,
                n.Body,
                n.IsRead,
                n.CreatedAt))
            .ToListAsync(cancellationToken);

        return PagedResult<UserNotificationDto>.Create(items, total, pagination.Page, pagination.PageSize);
    }

    public async Task MarkAllReadAsync(int userId, CancellationToken cancellationToken = default)
    {
        await _db.UserNotifications
            .Where(n => n.UserId == userId && !n.IsRead)
            .ExecuteUpdateAsync(setters => setters.SetProperty(n => n.IsRead, true), cancellationToken);
    }

    private static UserNotificationDto ToDto(UserNotification notification) => new(
        notification.Id,
        notification.Type,
        notification.Title,
        notification.Body,
        notification.IsRead,
        notification.CreatedAt
    );
}
