using FluentAssertions;
using Moq;
using TakeoutSaaS.Application.App.Tenants.Handlers;
using TakeoutSaaS.Application.App.Tenants.Queries;
using TakeoutSaaS.Application.Tests.TestUtilities;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.Tests.App.Tenants.Handlers;

public sealed class GetTenantsAnnouncementsQueryHandlerTests
{
    [Fact]
    public async Task GivenQuery_WhenHandle_ThenUsesTenantProviderAndOrdersAndPaginates()
    {
        // Arrange
        var tenantProvider = new Mock<ITenantProvider>();
        tenantProvider.Setup(x => x.GetCurrentTenantId()).Returns(42);

        var currentUserAccessor = new Mock<ICurrentUserAccessor>();
        currentUserAccessor.SetupGet(x => x.UserId).Returns(0);
        currentUserAccessor.SetupGet(x => x.IsAuthenticated).Returns(false);

        var announcements = new List<TakeoutSaaS.Domain.Tenants.Entities.TenantAnnouncement>
        {
            AnnouncementTestData.CreateAnnouncement(1, 42, priority: 1, effectiveFrom: DateTime.UtcNow.AddDays(-1)),
            AnnouncementTestData.CreateAnnouncement(2, 42, priority: 2, effectiveFrom: DateTime.UtcNow.AddDays(-3)),
            AnnouncementTestData.CreateAnnouncement(3, 42, priority: 2, effectiveFrom: DateTime.UtcNow.AddDays(-1)),
            AnnouncementTestData.CreateAnnouncement(4, 42, priority: 0, effectiveFrom: DateTime.UtcNow)
        };

        // 模拟数据库端排序：按 priority DESC, effectiveFrom DESC
        var sortedAnnouncements = announcements
            .OrderByDescending(x => x.Priority)
            .ThenByDescending(x => x.EffectiveFrom)
            .ToList();

        var announcementRepository = new Mock<ITenantAnnouncementRepository>();
        announcementRepository
            .Setup(x => x.SearchAsync(
                It.IsAny<long>(),
                It.IsAny<string?>(),
                It.IsAny<TakeoutSaaS.Domain.Tenants.Enums.AnnouncementStatus?>(),
                It.IsAny<TakeoutSaaS.Domain.Tenants.Enums.TenantAnnouncementType?>(),
                It.IsAny<bool?>(),
                It.IsAny<DateTime?>(),
                It.IsAny<DateTime?>(),
                It.IsAny<DateTime?>(),
                It.IsAny<bool>(),
                It.IsAny<int?>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(sortedAnnouncements);

        var announcementReadRepository = new Mock<ITenantAnnouncementReadRepository>();
        announcementReadRepository
            .Setup(x => x.GetByAnnouncementAsync(
                It.IsAny<long>(),
                It.IsAny<IEnumerable<long>>(),
                It.IsAny<long?>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(Array.Empty<TakeoutSaaS.Domain.Tenants.Entities.TenantAnnouncementRead>());

        var handler = new GetTenantsAnnouncementsQueryHandler(
            announcementRepository.Object,
            announcementReadRepository.Object,
            tenantProvider.Object,
            currentUserAccessor.Object);

        var query = new GetTenantsAnnouncementsQuery
        {
            TenantId = 999,
            Page = 2,
            PageSize = 2
        };

        // Act
        var result = await handler.Handle(query, CancellationToken.None);

        // Assert
        announcementRepository.Verify(x => x.SearchAsync(
            42,
            query.Keyword,
            query.Status,
            query.AnnouncementType,
            query.IsActive,
            query.EffectiveFrom,
            query.EffectiveTo,
            null,
            true,
            12,  // estimatedLimit = page * size * 3 = 2 * 2 * 3 = 12
            It.IsAny<CancellationToken>()), Times.Once);

        result.TotalCount.Should().Be(4);
        result.Items.Select(x => x.Id).Should().Equal(1, 4);
        result.Page.Should().Be(2);
        result.PageSize.Should().Be(2);
    }

    [Fact]
    public async Task GivenOnlyEffective_WhenHandle_ThenFiltersScheduledPublish()
    {
        // Arrange
        var tenantProvider = new Mock<ITenantProvider>();
        tenantProvider.Setup(x => x.GetCurrentTenantId()).Returns(42);

        var announcement1 = AnnouncementTestData.CreateAnnouncement(1, 42, priority: 1, effectiveFrom: DateTime.UtcNow.AddDays(-1));
        announcement1.ScheduledPublishAt = DateTime.UtcNow.AddMinutes(-10);

        var announcement2 = AnnouncementTestData.CreateAnnouncement(2, 42, priority: 1, effectiveFrom: DateTime.UtcNow.AddDays(-1));
        announcement2.ScheduledPublishAt = DateTime.UtcNow.AddMinutes(30);

        var announcements = new List<TakeoutSaaS.Domain.Tenants.Entities.TenantAnnouncement>
        {
            announcement1,
            announcement2
        };

        var announcementRepository = new Mock<ITenantAnnouncementRepository>();
        announcementRepository
            .Setup(x => x.SearchAsync(
                It.IsAny<long>(),
                It.IsAny<string?>(),
                It.IsAny<TakeoutSaaS.Domain.Tenants.Enums.AnnouncementStatus?>(),
                It.IsAny<TakeoutSaaS.Domain.Tenants.Enums.TenantAnnouncementType?>(),
                It.IsAny<bool?>(),
                It.IsAny<DateTime?>(),
                It.IsAny<DateTime?>(),
                It.IsAny<DateTime?>(),
                It.IsAny<bool>(),
                It.IsAny<int?>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(announcements);

        var announcementReadRepository = new Mock<ITenantAnnouncementReadRepository>();
        announcementReadRepository
            .Setup(x => x.GetByAnnouncementAsync(
                It.IsAny<long>(),
                It.IsAny<IEnumerable<long>>(),
                It.IsAny<long?>(),
                It.IsAny<CancellationToken>()))
            .ReturnsAsync(Array.Empty<TakeoutSaaS.Domain.Tenants.Entities.TenantAnnouncementRead>());

        var handler = new GetTenantsAnnouncementsQueryHandler(
            announcementRepository.Object,
            announcementReadRepository.Object,
            tenantProvider.Object);

        var query = new GetTenantsAnnouncementsQuery
        {
            OnlyEffective = true,
            Page = 1,
            PageSize = 10
        };

        // Act
        var result = await handler.Handle(query, CancellationToken.None);

        // Assert
        result.Items.Should().ContainSingle();
        result.Items[0].Id.Should().Be(1);
    }
}
