using CashLineERP.Application.Notifications;
using CashLineERP.Domain.Notifications;
using CashLineERP.SharedKernel.Notifications;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class NotificationOrchestrationRepository : INotificationOrchestrationRepository
{
    private readonly CashLineErpDbContext _dbContext;

    public NotificationOrchestrationRepository(CashLineErpDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IReadOnlyCollection<NotificationQueueItemDetailView> GetQueueItems(string? statusCode = null, string? channelCode = null)
    {
        var normalizedStatusCode = string.IsNullOrWhiteSpace(statusCode) ? null : statusCode.Trim().ToUpperInvariant();
        var normalizedChannelCode = string.IsNullOrWhiteSpace(channelCode) ? null : channelCode.Trim().ToUpperInvariant();

        return (
            from item in _dbContext.NotificationQueueItems.AsNoTracking()
            join template in _dbContext.NotificationTemplates.AsNoTracking()
                on item.NotificationTemplateId equals template.Id
            where normalizedStatusCode == null || item.StatusCode == normalizedStatusCode
            where normalizedChannelCode == null || item.ChannelCode == normalizedChannelCode
            orderby item.StatusCode, item.CreatedAtUtc, item.Id
            select Map(item, template))
            .ToArray();
    }

    public NotificationQueueItemDetailView? GetQueueItem(long notificationQueueItemId)
    {
        return (
            from item in _dbContext.NotificationQueueItems.AsNoTracking()
            join template in _dbContext.NotificationTemplates.AsNoTracking()
                on item.NotificationTemplateId equals template.Id
            where item.Id == notificationQueueItemId
            select Map(item, template))
            .SingleOrDefault();
    }

    public NotificationQueueItemDetailView Enqueue(EnqueueNotificationCommand command)
    {
        var template = _dbContext.NotificationTemplates
            .SingleOrDefault(entry => entry.Id == command.NotificationTemplateId);

        if (template is null || !template.IsActive)
        {
            throw new InvalidOperationException("An active notification template was not found.");
        }

        var renderedSubject = NotificationTemplateRenderer.Render(template.SubjectTemplate, command.Variables);
        var renderedBody = NotificationTemplateRenderer.Render(template.BodyTemplate, command.Variables)
            ?? throw new InvalidOperationException("Notification body rendering produced no content.");

        var queueItem = new NotificationQueueItem(
            notificationTemplateId: template.Id,
            templateCode: template.TemplateCode,
            channelCode: template.ChannelCode,
            recipientReference: command.RecipientReference.Trim(),
            contextCode: command.ContextCode,
            relatedEntityType: command.RelatedEntityType,
            relatedEntityId: command.RelatedEntityId,
            subject: renderedSubject,
            body: renderedBody);
        queueItem.Touch(command.ActorUserId);

        _dbContext.NotificationQueueItems.Add(queueItem);
        _dbContext.SaveChanges();
        return Map(queueItem, template);
    }

    public NotificationSendBatchResultView SendPendingBatch(NotificationSendBatchCommand command)
    {
        var senderCode = string.IsNullOrWhiteSpace(command.SenderCode) ? null : command.SenderCode.Trim().ToUpperInvariant();
        if (senderCode is null)
        {
            throw new InvalidOperationException("Notification sender code is required.");
        }

        if (command.BatchSize is < 1 or > 100)
        {
            throw new InvalidOperationException("Notification send batch size must be between 1 and 100.");
        }

        var nowUtc = DateTime.UtcNow;
        var queueItems = _dbContext.NotificationQueueItems
            .Where(entry =>
                (entry.StatusCode == "PENDING" || entry.StatusCode == "RETRY")
                && (entry.NextAttemptAtUtc == null || entry.NextAttemptAtUtc <= nowUtc))
            .OrderBy(entry => entry.CreatedAtUtc)
            .ThenBy(entry => entry.Id)
            .Take(command.BatchSize)
            .ToArray();

        foreach (var item in queueItems)
        {
            item.MarkSent(senderCode);
        }

        _dbContext.SaveChanges();

        var templatesById = _dbContext.NotificationTemplates
            .AsNoTracking()
            .Where(entry => queueItems.Select(item => item.NotificationTemplateId).Contains(entry.Id))
            .ToDictionary(entry => entry.Id);

        return new NotificationSendBatchResultView(
            senderCode,
            command.BatchSize,
            queueItems.Length,
            queueItems.Select(item => Map(item, templatesById[item.NotificationTemplateId])).ToArray());
    }

    public NotificationQueueItemDetailView MoveToDeadLetter(long notificationQueueItemId, long actorUserId, string reasonText)
    {
        var item = _dbContext.NotificationQueueItems.SingleOrDefault(entry => entry.Id == notificationQueueItemId)
            ?? throw new InvalidOperationException("Notification queue item was not found.");
        var template = _dbContext.NotificationTemplates.Single(entry => entry.Id == item.NotificationTemplateId);

        item.MoveToDeadLetter(reasonText);
        item.Touch(actorUserId);
        _dbContext.SaveChanges();
        return Map(item, template);
    }

    public NotificationQueueItemDetailView Replay(long notificationQueueItemId, long actorUserId, string replayReasonText, int delaySeconds)
    {
        if (delaySeconds is < 0 or > 86400)
        {
            throw new InvalidOperationException("Notification replay delay must be between 0 and 86400 seconds.");
        }

        var item = _dbContext.NotificationQueueItems.SingleOrDefault(entry => entry.Id == notificationQueueItemId)
            ?? throw new InvalidOperationException("Notification queue item was not found.");
        var template = _dbContext.NotificationTemplates.Single(entry => entry.Id == item.NotificationTemplateId);

        item.Replay(replayReasonText, DateTime.UtcNow.AddSeconds(delaySeconds));
        item.Touch(actorUserId);
        _dbContext.SaveChanges();
        return Map(item, template);
    }

    private static NotificationQueueItemDetailView Map(NotificationQueueItem item, NotificationTemplate template) =>
        new(
            item.Id,
            item.NotificationTemplateId,
            template.TemplateCode,
            item.ChannelCode,
            item.RecipientReference,
            item.ContextCode,
            item.RelatedEntityType,
            item.RelatedEntityId,
            item.Subject,
            item.Body,
            item.StatusCode,
            item.AttemptNumber,
            item.LastAttemptAtUtc,
            item.NextAttemptAtUtc,
            item.SentAtUtc,
            item.SentByProcess,
            item.ErrorText,
            item.CreatedAtUtc,
            item.CreatedByUserId);
}
