using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Contracts.Search;
using Baya.Application.Features.ServiceAreas.Commands.AddNurseServiceArea;
using Baya.Application.Features.ServiceAreas.Commands.RemoveNurseServiceArea;
using Baya.Domain.Entities.Geography;
using Baya.Domain.Entities.User;
using NSubstitute;
using NSubstitute.ReturnsExtensions;

namespace Baya.Test.Foundation.Geography;

public class NurseServiceAreaHandlersTests
{
    private readonly ICurrentUser _currentUser = Substitute.For<ICurrentUser>();
    private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
    private readonly INurseProfileRepository _nurses = Substitute.For<INurseProfileRepository>();
    private readonly IGeoRepository _geo = Substitute.For<IGeoRepository>();
    private readonly INurseServiceAreaRepository _areas = Substitute.For<INurseServiceAreaRepository>();
    private readonly ISearchIndexMaintainer _searchIndex = Substitute.For<ISearchIndexMaintainer>();

    public NurseServiceAreaHandlersTests()
    {
        _currentUser.UserId.Returns(7);
        _currentUser.Roles.Returns([RoleNames.Nurse]);
        _unitOfWork.NurseProfileRepository.Returns(_nurses);
        _unitOfWork.GeoRepository.Returns(_geo);
        _unitOfWork.NurseServiceAreaRepository.Returns(_areas);
        _nurses.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);
        _geo.GetCityAsync(Arg.Any<long>(), Arg.Any<CancellationToken>())
            .Returns(new City { NameFa = "تهران", NameEn = "Tehran" });
        _geo.IsCityActiveAsync(Arg.Any<long>(), Arg.Any<CancellationToken>()).Returns(true);
    }

    [Fact]
    public async Task Add_WholeCity_PersistsWholeCityRow()
    {
        _areas.DuplicateExistsAsync(42L, 101L, null, Arg.Any<CancellationToken>()).Returns(false);
        var handler = new AddNurseServiceAreaCommandHandler(_currentUser, _unitOfWork, _searchIndex);

        var result = await handler.Handle(new AddNurseServiceAreaCommand(101L, null), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.True(result.Result.IsWholeCity);
        await _areas.Received(1).AddAsync(
            Arg.Is<NurseServiceArea>(a => a.NurseId == 42L && a.CityId == 101L && a.DistrictId == null),
            Arg.Any<CancellationToken>());
        await _unitOfWork.Received(1).CommitAsync();
    }

    [Fact]
    public async Task Add_DuplicateWholeCity_ReturnsConflictNotPersisted()
    {
        _areas.DuplicateExistsAsync(42L, 101L, null, Arg.Any<CancellationToken>()).Returns(true);
        var handler = new AddNurseServiceAreaCommandHandler(_currentUser, _unitOfWork, _searchIndex);

        var result = await handler.Handle(new AddNurseServiceAreaCommand(101L, null), CancellationToken.None);

        Assert.True(result.IsConflict);
        await _areas.DidNotReceive().AddAsync(Arg.Any<NurseServiceArea>(), Arg.Any<CancellationToken>());
        await _unitOfWork.DidNotReceive().CommitAsync();
    }

    [Fact]
    public async Task Add_DuplicateDistrict_ReturnsConflict()
    {
        _geo.IsDistrictInActiveCityAsync(5L, 101L, Arg.Any<CancellationToken>()).Returns(true);
        _geo.GetDistrictAsync(5L, Arg.Any<CancellationToken>())
            .Returns(new District { NameFa = "منطقه ۱", NameEn = "District 1" });
        _areas.DuplicateExistsAsync(42L, 101L, 5L, Arg.Any<CancellationToken>()).Returns(true);
        var handler = new AddNurseServiceAreaCommandHandler(_currentUser, _unitOfWork, _searchIndex);

        var result = await handler.Handle(new AddNurseServiceAreaCommand(101L, 5L), CancellationToken.None);

        Assert.True(result.IsConflict);
    }

    [Fact]
    public async Task Add_DistrictNotInCity_Fails()
    {
        _geo.IsDistrictInActiveCityAsync(999L, 101L, Arg.Any<CancellationToken>()).Returns(false);
        var handler = new AddNurseServiceAreaCommandHandler(_currentUser, _unitOfWork, _searchIndex);

        var result = await handler.Handle(new AddNurseServiceAreaCommand(101L, 999L), CancellationToken.None);

        Assert.False(result.IsSuccess);
        await _areas.DidNotReceive().AddAsync(Arg.Any<NurseServiceArea>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Add_NonNurse_IsForbidden()
    {
        _currentUser.Roles.Returns([RoleNames.Customer]);
        var handler = new AddNurseServiceAreaCommandHandler(_currentUser, _unitOfWork, _searchIndex);

        var result = await handler.Handle(new AddNurseServiceAreaCommand(101L, null), CancellationToken.None);

        Assert.True(result.IsForbidden);
    }

    [Fact]
    public async Task Remove_OtherNursesArea_IsNotFound()
    {
        _areas.GetOwnedAsync(99L, 42L, Arg.Any<CancellationToken>()).ReturnsNull();
        var handler = new RemoveNurseServiceAreaCommandHandler(_currentUser, _unitOfWork, Substitute.For<IDateTimeProvider>(), _searchIndex);

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

        Assert.True(result.IsNotFound);
        await _unitOfWork.DidNotReceive().CommitAsync();
    }
}
