using Baya.Application.Contracts.Common;
using Baya.Application.Contracts.Persistence;
using Baya.Application.Contracts.Search;
using Baya.Application.Features.Variants.Commands.CreateVariant;
using Baya.Application.Models.Catalog;
using Baya.Domain.Entities.Catalog;
using Baya.Domain.Entities.User;
using NSubstitute;
using NSubstitute.ReturnsExtensions;

namespace Baya.Test.Foundation.Catalog;

public class CreateVariantHandlerTests
{
    private readonly ICurrentUser _currentUser = Substitute.For<ICurrentUser>();
    private readonly IUnitOfWork _unitOfWork = Substitute.For<IUnitOfWork>();
    private readonly INurseProfileRepository _nurses = Substitute.For<INurseProfileRepository>();
    private readonly ICatalogRepository _catalog = Substitute.For<ICatalogRepository>();
    private readonly INurseServiceVariantRepository _variants = Substitute.For<INurseServiceVariantRepository>();
    private readonly ISearchIndexMaintainer _searchIndex = Substitute.For<ISearchIndexMaintainer>();

    private const long CategoryId = 1;
    private const long ShiftGroupId = 10;
    private const long LiveInValueId = 100;
    private const long DaytimeValueId = 101;

    public CreateVariantHandlerTests()
    {
        _currentUser.UserId.Returns(7);
        _currentUser.Roles.Returns([RoleNames.Nurse]);
        _unitOfWork.NurseProfileRepository.Returns(_nurses);
        _unitOfWork.CatalogRepository.Returns(_catalog);
        _unitOfWork.NurseServiceVariantRepository.Returns(_variants);

        _nurses.GetProfileIdByUserIdAsync(7, Arg.Any<CancellationToken>()).Returns(42L);

        _catalog.GetActiveCategoryAsync(CategoryId, Arg.Any<CancellationToken>())
            .Returns(new ServiceCategoryDto(CategoryId, "مراقبت از سالمند", "Elderly Care", null, null, null, 1, true));

        _catalog.GetApplicableGroupsAsync(CategoryId, Arg.Any<CancellationToken>())
            .Returns(new List<OptionGroupDto>
            {
                new(ShiftGroupId, CategoryId, "نوع شیفت", "Shift type", IsRequired: true, SortOrder: 1, IsActive: true,
                    Values:
                    [
                        new OptionValueDto(LiveInValueId, "شبانه‌روزی", "Live-in", 1, true),
                        new OptionValueDto(DaytimeValueId, "روزانه", "Daytime", 2, true)
                    ])
            });

        _variants.DuplicateHashExistsAsync(Arg.Any<long>(), Arg.Any<long>(), Arg.Any<string>(), Arg.Any<long?>(), Arg.Any<CancellationToken>())
            .Returns(false);
    }

    private CreateVariantCommandHandler Handler() => new(_currentUser, _unitOfWork, _searchIndex);

    private static CreateVariantCommand Command(IReadOnlyList<VariantOptionSelection> options, string? displayName = null)
        => new(CategoryId, options, "8000000", "per_24h", null, displayName);

    [Fact]
    public async Task Create_ValidVariant_SucceedsWithGeneratedDisplayName()
    {
        var result = await Handler().Handle(Command([new(ShiftGroupId, LiveInValueId)]), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.True(result.Result.IsActive);
        Assert.Equal("8000000", result.Result.Price);
        Assert.Contains("مراقبت از سالمند", result.Result.DisplayName);
        Assert.Contains("شبانه‌روزی", result.Result.DisplayName);
        Assert.Single(result.Result.Options);
        await _variants.Received(1).AddAsync(
            Arg.Is<NurseServiceVariant>(v =>
                v.NurseId == 42L && v.ServiceCategoryId == CategoryId && v.PriceUnit == "per_24h"
                && v.Price == 8000000 && !string.IsNullOrEmpty(v.OptionSetHash) && v.Options.Count == 1),
            Arg.Any<CancellationToken>());
        await _unitOfWork.Received(1).CommitAsync();
    }

    [Fact]
    public async Task Create_MissingRequiredGroup_FailsValidationNotConflict()
    {
        var result = await Handler().Handle(Command([]), CancellationToken.None);

        Assert.False(result.IsSuccess);
        Assert.False(result.IsConflict);
        Assert.False(result.IsNotFound);
        await _variants.DidNotReceive().AddAsync(Arg.Any<NurseServiceVariant>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Create_DuplicateOptionSet_ReturnsConflict()
    {
        _variants.DuplicateHashExistsAsync(42L, CategoryId, Arg.Any<string>(), null, Arg.Any<CancellationToken>()).Returns(true);

        var result = await Handler().Handle(Command([new(ShiftGroupId, LiveInValueId)]), CancellationToken.None);

        Assert.True(result.IsConflict);
        await _variants.DidNotReceive().AddAsync(Arg.Any<NurseServiceVariant>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Create_SameGroupTwice_FailsOneValuePerDimension()
    {
        var result = await Handler().Handle(
            Command([new(ShiftGroupId, LiveInValueId), new(ShiftGroupId, DaytimeValueId)]), CancellationToken.None);

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

    [Fact]
    public async Task Create_ValueNotBelongingToGroup_Fails()
    {
        var result = await Handler().Handle(Command([new(ShiftGroupId, 999L)]), CancellationToken.None);

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

    [Fact]
    public async Task Create_InactiveOrMissingCategory_Fails()
    {
        _catalog.GetActiveCategoryAsync(CategoryId, Arg.Any<CancellationToken>()).ReturnsNull();

        var result = await Handler().Handle(Command([new(ShiftGroupId, LiveInValueId)]), CancellationToken.None);

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

    [Fact]
    public async Task Create_NonNurse_IsForbidden()
    {
        _currentUser.Roles.Returns([RoleNames.Customer]);

        var result = await Handler().Handle(Command([new(ShiftGroupId, LiveInValueId)]), CancellationToken.None);

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

    [Fact]
    public async Task Create_NurseOverridesDisplayName_UsesOverride()
    {
        var result = await Handler().Handle(
            Command([new(ShiftGroupId, LiveInValueId)], displayName: "My live-in package"), CancellationToken.None);

        Assert.True(result.IsSuccess);
        Assert.Equal("My live-in package", result.Result.DisplayName);
    }
}
