#nullable enable
using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Models.Addresses;
using Baya.Application.Models.Common;
using Baya.Domain.Entities.Geography;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.User;
using Mediator;

namespace Baya.Application.Features.Addresses.Commands.CreateAddress;

internal sealed class CreateAddressCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IGeocoder geocoder)
    : IRequestHandler<CreateAddressCommand, OperationResult<CustomerAddressDto>>
{
    public async ValueTask<OperationResult<CustomerAddressDto>> Handle(CreateAddressCommand request, CancellationToken cancellationToken)
    {
        if (currentUser.UserId is not { } userId)
            return OperationResult<CustomerAddressDto>.UnauthorizedResult("Not authenticated.");

        if (currentUser.Roles?.Contains(RoleNames.Customer) != true)
            return OperationResult<CustomerAddressDto>.ForbiddenResult("Only a customer can save an address.");

        var city = await unitOfWork.GeoRepository.GetCityAsync(request.CityId, cancellationToken);
        if (city is null || !await unitOfWork.GeoRepository.IsCityActiveAsync(request.CityId, cancellationToken))
            return OperationResult<CustomerAddressDto>.FailureResult(nameof(request.CityId), "City not found or inactive.");

        District? district = null;
        if (request.DistrictId is { } districtId)
        {
            if (!await unitOfWork.GeoRepository.IsDistrictInActiveCityAsync(districtId, request.CityId, cancellationToken))
                return OperationResult<CustomerAddressDto>.FailureResult(nameof(request.DistrictId), "District not found in this city, or inactive.");

            district = await unitOfWork.GeoRepository.GetDistrictAsync(districtId, cancellationToken);
        }

        var customerId = await unitOfWork.CustomerProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);

        var isFirst = customerId is not { } existing || !await unitOfWork.CustomerAddressRepository.HasAnyAsync(existing, cancellationToken);
        var isPrimary = request.IsPrimary || isFirst;

        // Prefer the client's dropped pin (the customer's exact door — best for the EVV distance check);
        // fall back to the server geocode only when no pin was sent.
        decimal? latitude;
        decimal? longitude;
        string geocodeSource;
        if (request is { Latitude: { } pinLat, Longitude: { } pinLng })
        {
            latitude = pinLat;
            longitude = pinLng;
            geocodeSource = GeocodeSources.UserPin;
        }
        else
        {
            var geo = await geocoder.GeocodeAsync(request.AddressLine, city.NameEn, district?.NameEn, cancellationToken);
            latitude = geo.Latitude;
            longitude = geo.Longitude;
            geocodeSource = GeocodeSources.Geocoder;
        }

        // Values are set as plaintext; the EF value converter encrypts the PII columns at rest.
        var address = new CustomerAddress
        {
            CityId = request.CityId,
            DistrictId = request.DistrictId,
            Title = request.Title,
            AddressLine = request.AddressLine,
            PostalCode = request.PostalCode,
            RecipientName = request.RecipientName,
            RecipientPhone = request.RecipientPhone,
            Latitude = latitude,
            Longitude = longitude,
            GeocodeSource = geocodeSource,
            IsPrimary = isPrimary
        };

        if (customerId is { } cid)
        {
            address.CustomerId = cid;
            if (isPrimary)
                await unitOfWork.CustomerAddressRepository.ClearOtherPrimaryAsync(cid, 0, cancellationToken);
        }
        else
        {
            // First address before any customer-profile save — provision the thin payer row so the FK is
            // fixed up on commit (mirrors the b3 patient auto-provision).
            var profile = new CustomerProfile { UserId = userId };
            await unitOfWork.CustomerProfileRepository.AddAsync(profile, cancellationToken);
            address.Customer = profile;
        }

        await unitOfWork.CustomerAddressRepository.AddAsync(address, cancellationToken);
        await unitOfWork.CommitAsync();

        return OperationResult<CustomerAddressDto>.SuccessResult(Build(address, city, district));
    }

    private static CustomerAddressDto Build(CustomerAddress address, City city, District? district) =>
        new(
            address.Id,
            address.Title,
            city.ProvinceId,
            city.Id,
            city.NameFa,
            city.NameEn,
            address.DistrictId,
            district?.NameFa,
            district?.NameEn,
            address.AddressLine,
            address.PostalCode,
            address.Latitude,
            address.Longitude,
            address.IsPrimary,
            address.RecipientName,
            address.RecipientPhone);
}
