using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Features.Identity.Commands.CreatePatient;
using Baya.Application.Features.Identity.Commands.UpdatePatient;
using Baya.Application.Features.Identity.Queries.GetPatient;
using Baya.Domain.Entities.Identity;
using Baya.Domain.Entities.User;
using NSubstitute;
using NSubstitute.ReturnsExtensions;

namespace Baya.Test.Foundation.Identity;

public class PatientHandlersTests
{
    private readonly ICurrentUser _currentUser = Substitute.For<ICurrentUser>();
    private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
    private readonly ICustomerProfileRepository _customers = Substitute.For<ICustomerProfileRepository>();
    private readonly IPatientRepository _patients = Substitute.For<IPatientRepository>();

    public PatientHandlersTests()
    {
        _currentUser.UserId.Returns(7);
        _currentUser.Roles.Returns([RoleNames.Customer]);
        _unitOfWork.CustomerProfileRepository.Returns(_customers);
        _unitOfWork.PatientRepository.Returns(_patients);
    }

    [Fact]
    public async Task Create_UnderExistingCustomer_UsesResolvedCustomerId()
    {
        _customers.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);
        var handler = new CreatePatientCommandHandler(_currentUser, _unitOfWork);

        var result = await handler.Handle(
            new CreatePatientCommand("Mother", "A", "B", new DateOnly(1950, 1, 1), "female", "O+", "notes"),
            CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Equal("female", result.Result.Gender);
        await _patients.Received(1).AddAsync(Arg.Is<Patient>(p => p.CustomerId == 42L && p.IsActive), Arg.Any<CancellationToken>());
        await _customers.DidNotReceive().AddAsync(Arg.Any<CustomerProfile>(), Arg.Any<CancellationToken>());
        await _unitOfWork.Received(1).CommitAsync();
    }

    [Fact]
    public async Task Create_NoCustomerProfileYet_AutoProvisionsProfile()
    {
        _customers.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns((long?)null);
        var handler = new CreatePatientCommandHandler(_currentUser, _unitOfWork);

        var result = await handler.Handle(
            new CreatePatientCommand("Mother", "A", "B", new DateOnly(1950, 1, 1), "female", "O+", "notes"),
            CancellationToken.None);

        Assert.True(result.IsSuccess);
        await _customers.Received(1).AddAsync(Arg.Is<CustomerProfile>(c => c.UserId == 7), Arg.Any<CancellationToken>());
        await _patients.Received(1).AddAsync(Arg.Any<Patient>(), Arg.Any<CancellationToken>());
    }

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

        var result = await handler.Handle(
            new CreatePatientCommand("Mother", "A", "B", new DateOnly(1950, 1, 1), "female", "O+", "notes"),
            CancellationToken.None);

        Assert.True(result.IsForbidden);
        await _patients.DidNotReceive().AddAsync(Arg.Any<Patient>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Update_OtherCustomersPatient_IsNotFound()
    {
        // Tenancy: the repo scopes by customerId, so a non-owned patient resolves to null → not-found.
        _customers.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);
        _patients.GetOwnedAsync(99L, 42L, Arg.Any<CancellationToken>()).ReturnsNull();
        var handler = new UpdatePatientCommandHandler(_currentUser, _unitOfWork);

        var result = await handler.Handle(
            new UpdatePatientCommand(99L, "X", "A", "B", new DateOnly(1960, 5, 5), "male", null, null),
            CancellationToken.None);

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

    [Fact]
    public async Task Get_OtherCustomersPatient_IsNotFound()
    {
        _customers.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);
        _patients.GetOwnedProjectedAsync(99L, 42L, Arg.Any<CancellationToken>()).ReturnsNull();
        var handler = new GetPatientQueryHandler(_currentUser, _unitOfWork);

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

        Assert.True(result.IsNotFound);
    }
}
