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

namespace Baya.Infrastructure.Persistence.Repositories;

internal sealed class CustomerAddressRepository : BaseAsyncRepository<CustomerAddress>, ICustomerAddressRepository
{
    public CustomerAddressRepository(ApplicationDbContext dbContext) : base(dbContext)
    {
    }

    public Task AddAsync(CustomerAddress address, CancellationToken cancellationToken)
        => base.AddAsync(address);

    public Task<CustomerAddress> GetOwnedAsync(long id, long customerId, CancellationToken cancellationToken)
        => Table.FirstOrDefaultAsync(a => a.Id == id && a.CustomerId == customerId, cancellationToken);

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

    public async Task ClearOtherPrimaryAsync(long customerId, long addressId, CancellationToken cancellationToken)
        => await Entities
            .Where(a => a.CustomerId == customerId && a.IsPrimary && a.Id != addressId)
            .ExecuteUpdateAsync(setters => setters.SetProperty(a => a.IsPrimary, false), cancellationToken);

    public async Task SetPrimaryAsync(long customerId, long addressId, CancellationToken cancellationToken)
    {
        // Clear-then-set inside one transaction so the filtered unique index is never momentarily violated.
        await using var transaction = await DbContext.Database.BeginTransactionAsync(cancellationToken);

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

        await Entities
            .Where(a => a.Id == addressId && a.CustomerId == customerId)
            .ExecuteUpdateAsync(setters => setters.SetProperty(a => a.IsPrimary, true), cancellationToken);

        await transaction.CommitAsync(cancellationToken);
    }

    public async Task<PagedResult<CustomerAddressDto>> ListAsync(long customerId, int page, int pageSize, CancellationToken cancellationToken)
    {
        var query = TableNoTracking.Where(a => a.CustomerId == customerId);

        var total = await query.CountAsync(cancellationToken);
        var items = await query
            .OrderByDescending(a => a.IsPrimary)
            .ThenByDescending(a => a.Id)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            // The encrypted columns are decrypted by the value converter on materialization — the owner's
            // own read is the only path that returns the plaintext address.
            .Select(a => new CustomerAddressDto(
                a.Id,
                a.Title,
                a.City.ProvinceId,
                a.CityId,
                a.City.NameFa,
                a.City.NameEn,
                a.DistrictId,
                a.DistrictId == null ? null : a.District.NameFa,
                a.DistrictId == null ? null : a.District.NameEn,
                a.AddressLine,
                a.PostalCode,
                a.Latitude,
                a.Longitude,
                a.IsPrimary,
                a.RecipientName,
                a.RecipientPhone))
            .ToListAsync(cancellationToken);

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