using CashLineERP.Application.Integration;
using CashLineERP.Domain.Integration;
using Microsoft.EntityFrameworkCore;

namespace CashLineERP.Infrastructure.Persistence.Repositories;

public sealed class OutboxEventRepository : IOutboxEventRepository
{
    private readonly CashLineErpDbContext _dbContext;

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

    public IReadOnlyCollection<OutboxEventDetailView> GetOutboxEvents(string? statusCode = null, string? sourceModuleCode = null)
    {
        var normalizedStatusCode = string.IsNullOrWhiteSpace(statusCode) ? null : statusCode.Trim().ToUpperInvariant();
        var normalizedSourceModuleCode = string.IsNullOrWhiteSpace(sourceModuleCode) ? null : sourceModuleCode.Trim().ToUpperInvariant();

        return _dbContext.OutboxEvents
            .AsNoTracking()
            .Where(entry => normalizedStatusCode == null || entry.StatusCode == normalizedStatusCode)
            .Where(entry => normalizedSourceModuleCode == null || entry.SourceModuleCode == normalizedSourceModuleCode)
            .OrderBy(entry => entry.StatusCode)
            .ThenBy(entry => entry.AvailableAtUtc)
            .ThenBy(entry => entry.EventNumber)
            .ToArray()
            .Select(Map)
            .ToArray();
    }

    public OutboxEventDetailView? GetOutboxEvent(long outboxEventId)
    {
        var outboxEvent = _dbContext.OutboxEvents
            .AsNoTracking()
            .SingleOrDefault(entry => entry.Id == outboxEventId);

        return outboxEvent is null ? null : Map(outboxEvent);
    }

    public PublishOutboxBatchResultView PublishPendingEvents(PublishOutboxBatchCommand command)
    {
        var publisherCode = command.PublisherCode?.Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(publisherCode))
        {
            throw new InvalidOperationException("Outbox publisher code is required.");
        }

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

        var nowUtc = DateTime.UtcNow;
        var pendingEvents = _dbContext.OutboxEvents
            .Where(entry =>
                (entry.StatusCode == "PENDING" || entry.StatusCode == "RETRY")
                && entry.AvailableAtUtc <= nowUtc)
            .OrderBy(entry => entry.AvailableAtUtc)
            .ThenBy(entry => entry.Id)
            .Take(command.BatchSize)
            .ToArray();

        foreach (var outboxEvent in pendingEvents)
        {
            outboxEvent.MarkPublished(publisherCode);
        }

        _dbContext.SaveChanges();

        return new PublishOutboxBatchResultView(
            publisherCode,
            command.BatchSize,
            pendingEvents.Length,
            pendingEvents.Select(Map).ToArray());
    }

    public OutboxEventDetailView MoveToDeadLetter(long outboxEventId, long actorUserId, string reasonText)
    {
        var outboxEvent = _dbContext.OutboxEvents.SingleOrDefault(entry => entry.Id == outboxEventId);
        if (outboxEvent is null)
        {
            throw new InvalidOperationException("Outbox event was not found.");
        }

        outboxEvent.MoveToDeadLetter(reasonText);
        outboxEvent.Touch(actorUserId);
        _dbContext.SaveChanges();

        return Map(outboxEvent);
    }

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

        var outboxEvent = _dbContext.OutboxEvents.SingleOrDefault(entry => entry.Id == outboxEventId);
        if (outboxEvent is null)
        {
            throw new InvalidOperationException("Outbox event was not found.");
        }

        outboxEvent.Replay(replayReasonText, DateTime.UtcNow.AddSeconds(delaySeconds));
        outboxEvent.Touch(actorUserId);
        _dbContext.SaveChanges();

        return Map(outboxEvent);
    }

    private static OutboxEventDetailView Map(OutboxEvent outboxEvent) =>
        new(
            outboxEvent.Id,
            outboxEvent.EventNumber,
            outboxEvent.SourceModuleCode,
            outboxEvent.AggregateType,
            outboxEvent.AggregateId,
            outboxEvent.EventType,
            outboxEvent.CorrelationKey,
            outboxEvent.PayloadText,
            outboxEvent.StatusCode,
            outboxEvent.AttemptNumber,
            outboxEvent.AvailableAtUtc,
            outboxEvent.PublishedAtUtc,
            outboxEvent.PublishedByProcess,
            outboxEvent.LastErrorText,
            outboxEvent.CreatedAtUtc);
}
