using Baya.Application.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Models.Identity;
using Baya.Domain.Entities.Identity;
using Baya.Infrastructure.Persistence.Repositories.Common;
using Microsoft.EntityFrameworkCore;

namespace Baya.Infrastructure.Persistence.Repositories;

internal sealed class NurseBankAccountRepository : BaseAsyncRepository<NurseBankAccount>, INurseBankAccountRepository
{
    public NurseBankAccountRepository(ApplicationDbContext dbContext) : base(dbContext)
    {
    }

    public Task AddAsync(NurseBankAccount account, CancellationToken cancellationToken)
        => base.AddAsync(account);

    public Task<NurseBankAccount> GetOwnedAsync(long id, long nurseId, CancellationToken cancellationToken)
        => Table.FirstOrDefaultAsync(a => a.Id == id && a.NurseId == nurseId, cancellationToken);

    public async Task SetPrimaryAsync(long nurseId, long accountId, CancellationToken cancellationToken)
    {
        // Clear-then-set inside one transaction: two ordered statements so the filtered unique index is
        // never momentarily violated (setting the new primary while the old one is still primary).
        await using var transaction = await DbContext.Database.BeginTransactionAsync(cancellationToken);

        await Entities
            .Where(a => a.NurseId == nurseId && a.IsPrimary)
            .ExecuteUpdateAsync(setters => setters.SetProperty(a => a.IsPrimary, false), cancellationToken);

        await Entities
            .Where(a => a.Id == accountId && a.NurseId == nurseId)
            .ExecuteUpdateAsync(setters => setters.SetProperty(a => a.IsPrimary, true), cancellationToken);

        await transaction.CommitAsync(cancellationToken);
    }

    public Task<bool> IbanHashExistsAsync(string ibanHash, CancellationToken cancellationToken)
        => TableNoTracking.AnyAsync(a => a.IbanHash == ibanHash, cancellationToken);

    public Task<bool> HasAnyAsync(long nurseId, CancellationToken cancellationToken)
        => TableNoTracking.AnyAsync(a => a.NurseId == nurseId, cancellationToken);

    public Task<NurseBankAccount> GetPrimaryAsync(long nurseId, CancellationToken cancellationToken)
        => Table.FirstOrDefaultAsync(a => a.NurseId == nurseId && a.IsPrimary, cancellationToken);

    public async Task<IReadOnlyList<NurseBankAccountDto>> ListAsync(long nurseId, CancellationToken cancellationToken)
    {
        // Decrypt the IBAN in memory, then mask to last-4 — the full value never leaves the repository.
        var rows = await TableNoTracking
            .Where(a => a.NurseId == nurseId)
            .OrderByDescending(a => a.IsPrimary)
            .ThenByDescending(a => a.Id)
            .Select(a => new { a.Id, a.BankName, a.Iban, a.IsPrimary, a.IsVerified, a.MatchedNationalId })
            .ToListAsync(cancellationToken);

        return rows
            .Select(a => new NurseBankAccountDto(a.Id, a.BankName, Mask.IbanTail(a.Iban), a.IsPrimary, a.IsVerified, a.MatchedNationalId))
            .ToList();
    }
}
