#nullable enable
using Baya.Application.Contracts.Persistence;
using Baya.Application.Models.Invoices;
using Baya.Domain.Entities.Booking;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.Invoices;
using Baya.Infrastructure.Persistence.Repositories.Common;
using Microsoft.EntityFrameworkCore;

namespace Baya.Infrastructure.Persistence.Repositories;

internal sealed class InvoiceRepository : BaseAsyncRepository<Invoice>, IInvoiceRepository
{
    public InvoiceRepository(ApplicationDbContext dbContext) : base(dbContext)
    {
    }

    public Task<InvoiceBookingAmounts?> GetBookingAmountsAsync(long bookingId, CancellationToken cancellationToken)
        => (from b in DbContext.Set<Booking>().AsNoTracking()
            where b.Id == bookingId
            join c in DbContext.Set<CustomerProfile>() on b.CustomerId equals c.Id
            select new InvoiceBookingAmounts(
                b.Id,
                c.UserId,
                b.GrossPriceIrr,
                b.BalinyaarCommissionIrr,
                (long?)null))
            .FirstOrDefaultAsync(cancellationToken);

    public Task<Invoice?> GetTrackedByBookingIdAsync(long bookingId, CancellationToken cancellationToken)
        => Table.FirstOrDefaultAsync(i => i.BookingId == bookingId, cancellationToken);

    public async Task<InvoiceProjection?> GetByBookingIdAsync(long bookingId, CancellationToken cancellationToken)
    {
        var row = await (from i in TableNoTracking
                         where i.BookingId == bookingId
                         join b in DbContext.Set<Booking>() on i.BookingId equals b.Id
                         join c in DbContext.Set<CustomerProfile>() on b.CustomerId equals c.Id
                         select new { CustomerUserId = c.UserId, Invoice = i })
            .FirstOrDefaultAsync(cancellationToken);

        if (row is null)
            return null;

        // The PDF URL is resolved by the handler through IObjectStorage — the repo carries the raw key.
        var dto = new InvoiceDto(
            row.Invoice.Id, row.Invoice.BookingId, row.Invoice.InvoiceNumber, row.Invoice.IssuingEntityType,
            row.Invoice.GrossIrr.ToString(), row.Invoice.PlatformCommissionIrr.ToString(),
            row.Invoice.BnplCommissionIrr?.ToString(), row.Invoice.VatRate, row.Invoice.VatIrr.ToString(),
            (row.Invoice.PlatformCommissionIrr + (row.Invoice.BnplCommissionIrr ?? 0) + row.Invoice.VatIrr).ToString(),
            row.Invoice.MoadianReferenceNumber, row.Invoice.MoadianStatus, PdfUrl: null, row.Invoice.IssuedAt);

        return new InvoiceProjection(row.CustomerUserId, row.Invoice.PdfStorageKey, dto);
    }

    public async Task<IReadOnlyList<Invoice>> GetUnregisteredMoadianInvoicesAsync(int max, CancellationToken cancellationToken)
        => await Table
            .Where(i => i.MoadianStatus == MoadianStatus.Pending || i.MoadianStatus == MoadianStatus.Submitted)
            .OrderBy(i => i.Id)
            .Take(max)
            .ToListAsync(cancellationToken);

    public Task AddInvoiceAsync(Invoice invoice, CancellationToken cancellationToken)
        => base.AddAsync(invoice);

    public async Task<long> ReserveNextInvoiceNumberAsync(CancellationToken cancellationToken)
    {
        var counter = await DbContext.Set<InvoiceNumberSequence>().FirstOrDefaultAsync(s => s.Id == 1, cancellationToken)
            ?? throw new InvalidOperationException("The invoice-number counter row is missing.");

        var reserved = counter.NextValue;
        counter.NextValue = reserved + 1;
        return reserved;
    }
}
