#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.UpdateAddress;

internal sealed class UpdateAddressCommandHandler(
    ICurrentUser currentUser,
    IUnitOfWork unitOfWork,
    IGeocoder geocoder)
    : IRequestHandler<UpdateAddressCommand, OperationResult<CustomerAddressDto>>
{
    public async ValueTask<OperationResult<CustomerAddressDto>> Handle(UpdateAddressCommand 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 edit an address.");

        var customerId = await unitOfWork.CustomerProfileRepository.GetProfileIdByUserIdAsync(userId, cancellationToken);
        if (customerId is not { } cid)
            return OperationResult<CustomerAddressDto>.NotFoundResult("Address not found.");

        var address = await unitOfWork.CustomerAddressRepository.GetOwnedAsync(request.Id, cid, cancellationToken);
        if (address is null)
            return OperationResult<CustomerAddressDto>.NotFoundResult("Address not found.");

        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 locationChanged =
            address.CityId != request.CityId ||
            address.DistrictId != request.DistrictId ||
            !string.Equals(address.AddressLine, request.AddressLine, StringComparison.Ordinal);

        address.Title = request.Title;
        address.CityId = request.CityId;
        address.DistrictId = request.DistrictId;
        address.AddressLine = request.AddressLine;
        address.PostalCode = request.PostalCode;
        address.RecipientName = request.RecipientName;
        address.RecipientPhone = request.RecipientPhone;

        // A freshly-dropped pin always wins; otherwise re-geocode only when the location text changed.
        if (request is { Latitude: { } pinLat, Longitude: { } pinLng })
        {
            address.Latitude = pinLat;
            address.Longitude = pinLng;
            address.GeocodeSource = GeocodeSources.UserPin;
        }
        else if (locationChanged)
        {
            var geo = await geocoder.GeocodeAsync(request.AddressLine, city.NameEn, district?.NameEn, cancellationToken);
            address.Latitude = geo.Latitude;
            address.Longitude = geo.Longitude;
            address.GeocodeSource = GeocodeSources.Geocoder;
        }

        await unitOfWork.CommitAsync();

        return OperationResult<CustomerAddressDto>.SuccessResult(new CustomerAddressDto(
            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));
    }
}
