using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Features.Addresses.Commands.CreateAddress;
using Baya.Application.Features.Addresses.Commands.SetPrimaryAddress;
using Baya.Domain.Entities.Geography;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.User;
using NSubstitute;
using NSubstitute.ReturnsExtensions;

namespace Baya.Test.Foundation.Geography;

public class CustomerAddressHandlersTests
{
    private readonly ICurrentUser _currentUser = Substitute.For<ICurrentUser>();
    private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
    private readonly ICustomerProfileRepository _customers = Substitute.For<ICustomerProfileRepository>();
    private readonly ICustomerAddressRepository _addresses = Substitute.For<ICustomerAddressRepository>();
    private readonly IGeoRepository _geo = Substitute.For<IGeoRepository>();
    private readonly IGeocoder _geocoder = Substitute.For<IGeocoder>();

    public CustomerAddressHandlersTests()
    {
        _currentUser.UserId.Returns(7);
        _currentUser.Roles.Returns([RoleNames.Customer]);
        _unitOfWork.CustomerProfileRepository.Returns(_customers);
        _unitOfWork.CustomerAddressRepository.Returns(_addresses);
        _unitOfWork.GeoRepository.Returns(_geo);
        _geo.GetCityAsync(Arg.Any<long>(), Arg.Any<CancellationToken>())
            .Returns(new City { NameFa = "تهران", NameEn = "Tehran" });
        _geo.IsCityActiveAsync(Arg.Any<long>(), Arg.Any<CancellationToken>()).Returns(true);
    }

    private CreateAddressCommand Body(bool isPrimary = false) =>
        new("خانه", 101L, null, "خیابان ولیعصر، پلاک ۱۰", "1234567890", "علی", "09120000000", isPrimary);

    [Fact]
    public async Task Create_FirstAddress_IsPrimaryAndGeocoded()
    {
        _customers.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);
        _addresses.HasAnyAsync(42L, Arg.Any<CancellationToken>()).Returns(false);
        _geocoder.GeocodeAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(new GeocodeResult(35.6892m, 51.3890m, "Tehran", 0.9));
        var handler = new CreateAddressCommandHandler(_currentUser, _unitOfWork, _geocoder);

        var result = await handler.Handle(Body(), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Equal(35.6892m, result.Result.Latitude);
        await _addresses.Received(1).AddAsync(
            Arg.Is<CustomerAddress>(a => a.IsPrimary && a.Latitude == 35.6892m && a.CustomerId == 42L),
            Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Create_SecondAddressAsPrimary_ClearsPriorPrimary()
    {
        _customers.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);
        _addresses.HasAnyAsync(42L, Arg.Any<CancellationToken>()).Returns(true);
        _geocoder.GeocodeAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(new GeocodeResult(1m, 1m, "x", 0.9));
        var handler = new CreateAddressCommandHandler(_currentUser, _unitOfWork, _geocoder);

        var result = await handler.Handle(Body(isPrimary: true), CancellationToken.None);

        Assert.True(result.IsSuccess);
        await _addresses.Received(1).ClearOtherPrimaryAsync(42L, 0L, Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Create_LowConfidenceGeocode_SavedWithoutCoordinates()
    {
        _customers.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);
        _addresses.HasAnyAsync(42L, Arg.Any<CancellationToken>()).Returns(false);
        _geocoder.GeocodeAsync(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<string>(), Arg.Any<CancellationToken>())
            .Returns(new GeocodeResult(null, null, "x", 0.2));
        var handler = new CreateAddressCommandHandler(_currentUser, _unitOfWork, _geocoder);

        var result = await handler.Handle(Body(), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Null(result.Result.Latitude);
        await _addresses.Received(1).AddAsync(
            Arg.Is<CustomerAddress>(a => a.Latitude == null && a.Longitude == null),
            Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Create_NonCustomer_IsForbidden()
    {
        _currentUser.Roles.Returns([RoleNames.Nurse]);
        var handler = new CreateAddressCommandHandler(_currentUser, _unitOfWork, _geocoder);

        var result = await handler.Handle(Body(), CancellationToken.None);

        Assert.True(result.IsForbidden);
    }

    [Fact]
    public async Task SetPrimary_OtherCustomersAddress_IsNotFound()
    {
        _customers.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);
        _addresses.GetOwnedAsync(99L, 42L, Arg.Any<CancellationToken>()).ReturnsNull();
        var handler = new SetPrimaryAddressCommandHandler(_currentUser, _unitOfWork);

        var result = await handler.Handle(new SetPrimaryAddressCommand(99L), CancellationToken.None);

        Assert.True(result.IsNotFound);
        await _addresses.DidNotReceive().SetPrimaryAsync(Arg.Any<long>(), Arg.Any<long>(), Arg.Any<CancellationToken>());
    }
}
