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

namespace Baya.Infrastructure.Persistence.Repositories;

internal sealed class PartnerCenterRepository : BaseAsyncRepository<PartnerCenter>, IPartnerCenterRepository
{
    private const int DashboardNurseCap = 50;

    public PartnerCenterRepository(ApplicationDbContext dbContext) : base(dbContext)
    {
    }

    public Task AddAsync(PartnerCenter center, CancellationToken cancellationToken) => base.AddAsync(center);

    public Task<PartnerCenter?> GetTrackedAsync(long centerId, CancellationToken cancellationToken)
        => Entities.FirstOrDefaultAsync(c => c.Id == centerId, cancellationToken);

    public Task<bool> ExistsAsync(long centerId, CancellationToken cancellationToken)
        => Entities.AnyAsync(c => c.Id == centerId, cancellationToken);

    public async Task<PartnerCenterDetailDto?> GetDetailAsync(long centerId, CancellationToken cancellationToken)
    {
        // Project (the converter decrypts settlement_iban here); the plaintext IBAN is masked in memory below and
        // never leaves this method.
        var raw = await Entities.AsNoTracking()
            .Where(c => c.Id == centerId)
            .Select(c => new
            {
                c.Id, c.Name, c.LegalEntityType, c.MohEstablishmentPermitNo, c.TechnicalDirectorNurseUserId,
                c.TechnicalDirectorLicenseNo, c.EnamadCode, c.SettlementIban, c.IsMerchantOfRecord, c.CommissionRate,
                c.AdminUserId, c.IsActive, c.VerifiedAt, c.CreatedAt,
                SponsoredCount = DbContext.Set<NurseProfile>().Count(n => n.PartnerCenterId == c.Id)
            })
            .FirstOrDefaultAsync(cancellationToken);

        if (raw is null)
            return null;

        return new PartnerCenterDetailDto(
            raw.Id, raw.Name, raw.LegalEntityType, raw.MohEstablishmentPermitNo, raw.TechnicalDirectorNurseUserId,
            raw.TechnicalDirectorLicenseNo, raw.EnamadCode,
            string.IsNullOrEmpty(raw.SettlementIban) ? null : Mask.IbanTail(raw.SettlementIban),
            raw.IsMerchantOfRecord, raw.CommissionRate, raw.AdminUserId, raw.IsActive, raw.VerifiedAt,
            raw.SponsoredCount, raw.CreatedAt);
    }

    public async Task<PagedResult<PartnerCenterListItemDto>> ListAsync(bool? isActive, int page, int pageSize, CancellationToken cancellationToken)
    {
        var query = Entities.AsNoTracking().AsQueryable();
        if (isActive is { } active)
            query = query.Where(c => c.IsActive == active);

        var total = await query.CountAsync(cancellationToken);
        var items = await query
            .OrderByDescending(c => c.Id)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(c => new PartnerCenterListItemDto(
                c.Id, c.Name, c.LegalEntityType, c.IsMerchantOfRecord, c.IsActive, c.VerifiedAt,
                DbContext.Set<NurseProfile>().Count(n => n.PartnerCenterId == c.Id),
                c.AdminUserId))
            .ToListAsync(cancellationToken);

        return new PagedResult<PartnerCenterListItemDto>(items, total, page, pageSize);
    }

    public async Task<CenterForBookingDto?> ResolveCenterForBookingAsync(long bookingId, CancellationToken cancellationToken)
    {
        var booking = await DbContext.Set<Booking>().AsNoTracking()
            .Where(b => b.Id == bookingId)
            .Select(b => new { b.NurseId })
            .FirstOrDefaultAsync(cancellationToken);

        if (booking is null)
            return null;

        var centerId = await DbContext.Set<NurseProfile>().AsNoTracking()
            .Where(n => n.Id == booking.NurseId)
            .Select(n => n.PartnerCenterId)
            .FirstOrDefaultAsync(cancellationToken);

        if (centerId is { } id)
        {
            var center = await Entities.AsNoTracking()
                .Where(c => c.Id == id)
                .Select(c => new { c.Id, c.Name, c.IsMerchantOfRecord })
                .FirstOrDefaultAsync(cancellationToken);

            // Merchant-of-record center → it is the invoice issuer + settlement target. Otherwise the platform
            // issues (a non-MoR sponsor does not change the issuer).
            if (center is { IsMerchantOfRecord: true })
                return new CenterForBookingDto(bookingId, InvoiceIssuingEntityType.PartnerCenter, center.Id, center.Name, true);
        }

        return new CenterForBookingDto(bookingId, InvoiceIssuingEntityType.Platform, null, null, false);
    }

    public Task<int?> GetAdminUserIdAsync(long centerId, CancellationToken cancellationToken)
        => Entities.AsNoTracking()
            .Where(c => c.Id == centerId)
            .Select(c => (int?)c.AdminUserId)
            .FirstOrDefaultAsync(cancellationToken);

    public async Task<CenterDashboardDto?> GetDashboardAsync(long centerId, CancellationToken cancellationToken)
    {
        var center = await Entities.AsNoTracking()
            .Where(c => c.Id == centerId)
            .Select(c => new { c.Id, c.Name, c.IsMerchantOfRecord, c.IsActive, c.SettlementIban })
            .FirstOrDefaultAsync(cancellationToken);

        if (center is null)
            return null;

        var sponsoredNurseCount = await DbContext.Set<NurseProfile>().AsNoTracking()
            .CountAsync(n => n.PartnerCenterId == centerId, cancellationToken);

        var sponsoredNurses = await DbContext.Set<NurseProfile>().AsNoTracking()
            .Where(n => n.PartnerCenterId == centerId)
            .OrderBy(n => n.Id)
            .Take(DashboardNurseCap)
            .Select(n => new SponsoredNurseDto(n.Id, n.UserId, n.IsVerified, n.AverageRating, n.TotalCompletedBookings))
            .ToListAsync(cancellationToken);

        var sponsoredBookingCount = await DbContext.Set<Booking>().AsNoTracking()
            .CountAsync(b => DbContext.Set<NurseProfile>().Any(n => n.Id == b.NurseId && n.PartnerCenterId == centerId), cancellationToken);

        var invoiceCount = await DbContext.Set<Invoice>().AsNoTracking()
            .CountAsync(i => i.PartnerCenterId == centerId, cancellationToken);

        return new CenterDashboardDto(
            center.Id, center.Name, center.IsMerchantOfRecord, center.IsActive,
            string.IsNullOrEmpty(center.SettlementIban) ? null : Mask.IbanTail(center.SettlementIban),
            sponsoredNurseCount, sponsoredBookingCount, invoiceCount, sponsoredNurses);
    }
}
