using FluentAssertions;
using Microsoft.EntityFrameworkCore;
using Moq;
using TakeoutSaaS.Application.App.Tenants.Commands;
using TakeoutSaaS.Application.App.Tenants.Handlers;
using TakeoutSaaS.Application.Messaging.Abstractions;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Infrastructure.App.Repositories;
using TakeoutSaaS.Integration.Tests.Fixtures;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;

namespace TakeoutSaaS.Integration.Tests.App.Tenants;

public sealed class AnnouncementWorkflowTests
{
    [Fact]
    public async Task GivenDraftAnnouncement_WhenPublish_ThenStatusIsPublishedAndActive()
    {
        // Arrange
        using var database = new SqliteTestDatabase();
        using var context = database.CreateContext(tenantId: 100, userId: 11);

        var announcement = CreateDraftAnnouncement(tenantId: 100, id: 9001);
        context.TenantAnnouncements.Add(announcement);
        await context.SaveChangesAsync();
        context.ChangeTracker.Clear();

        var repository = new EfTenantAnnouncementRepository(context);
        var tenantProvider = new TestTenantProvider(100);
        var eventPublisher = new Mock<IEventPublisher>();
        var handler = new PublishAnnouncementCommandHandler(repository, tenantProvider, eventPublisher.Object);

        // Act
        var result = await handler.Handle(new PublishAnnouncementCommand
        {
            AnnouncementId = announcement.Id,
            RowVersion = announcement.RowVersion
        }, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result!.Status.Should().Be(AnnouncementStatus.Published);
        result.IsActive.Should().BeTrue();

        using var verifyContext = database.CreateContext(tenantId: 100);
        var persisted = await verifyContext.TenantAnnouncements.FirstAsync(x => x.Id == announcement.Id);
        persisted.Status.Should().Be(AnnouncementStatus.Published);
        persisted.PublishedAt.Should().NotBeNull();
    }

    [Fact]
    public async Task GivenPublishedAnnouncement_WhenRevoke_ThenStatusIsRevokedAndInactive()
    {
        // Arrange
        using var database = new SqliteTestDatabase();
        using var context = database.CreateContext(tenantId: 200, userId: 11);

        var announcement = CreateDraftAnnouncement(tenantId: 200, id: 9002);
        announcement.Status = AnnouncementStatus.Published;
        context.TenantAnnouncements.Add(announcement);
        await context.SaveChangesAsync();
        context.ChangeTracker.Clear();

        var repository = new EfTenantAnnouncementRepository(context);
        var tenantProvider = new TestTenantProvider(200);
        var eventPublisher = new Mock<IEventPublisher>();
        var handler = new RevokeAnnouncementCommandHandler(repository, tenantProvider, eventPublisher.Object);

        // Act
        var result = await handler.Handle(new RevokeAnnouncementCommand
        {
            AnnouncementId = announcement.Id,
            RowVersion = announcement.RowVersion
        }, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result!.Status.Should().Be(AnnouncementStatus.Revoked);
        result.IsActive.Should().BeFalse();

        using var verifyContext = database.CreateContext(tenantId: 200);
        var persisted = await verifyContext.TenantAnnouncements.FirstAsync(x => x.Id == announcement.Id);
        persisted.Status.Should().Be(AnnouncementStatus.Revoked);
        persisted.RevokedAt.Should().NotBeNull();
    }

    [Fact]
    public async Task GivenRevokedAnnouncement_WhenPublish_ThenRepublishAndClearRevokedAt()
    {
        // Arrange
        using var database = new SqliteTestDatabase();
        using var context = database.CreateContext(tenantId: 300, userId: 11);

        var announcement = CreateDraftAnnouncement(tenantId: 300, id: 9003);
        announcement.Status = AnnouncementStatus.Revoked;
        announcement.RevokedAt = DateTime.UtcNow.AddMinutes(-5);
        context.TenantAnnouncements.Add(announcement);
        await context.SaveChangesAsync();
        context.ChangeTracker.Clear();

        var repository = new EfTenantAnnouncementRepository(context);
        var tenantProvider = new TestTenantProvider(300);
        var eventPublisher = new Mock<IEventPublisher>();
        var handler = new PublishAnnouncementCommandHandler(repository, tenantProvider, eventPublisher.Object);

        // Act
        var result = await handler.Handle(new PublishAnnouncementCommand
        {
            AnnouncementId = announcement.Id,
            RowVersion = announcement.RowVersion
        }, CancellationToken.None);

        // Assert
        result.Should().NotBeNull();
        result!.Status.Should().Be(AnnouncementStatus.Published);
        result.IsActive.Should().BeTrue();

        using var verifyContext = database.CreateContext(tenantId: 300);
        var persisted = await verifyContext.TenantAnnouncements.FirstAsync(x => x.Id == announcement.Id);
        persisted.Status.Should().Be(AnnouncementStatus.Published);
        persisted.RevokedAt.Should().BeNull();
    }

    [Fact]
    public async Task GivenPublishedAnnouncement_WhenUpdate_ThenThrowsBusinessException()
    {
        // Arrange
        using var database = new SqliteTestDatabase();
        using var context = database.CreateContext(tenantId: 400, userId: 11);

        var announcement = CreateDraftAnnouncement(tenantId: 400, id: 9004);
        announcement.Status = AnnouncementStatus.Published;
        context.TenantAnnouncements.Add(announcement);
        await context.SaveChangesAsync();
        context.ChangeTracker.Clear();

        var repository = new EfTenantAnnouncementRepository(context);
        var handler = new UpdateTenantAnnouncementCommandHandler(repository);

        var command = new UpdateTenantAnnouncementCommand
        {
            TenantId = 400,
            AnnouncementId = announcement.Id,
            Title = "更新标题",
            Content = "更新内容",
            TargetType = "ALL_TENANTS",
            RowVersion = announcement.RowVersion
        };

        // Act
        Func<Task> act = async () => await handler.Handle(command, CancellationToken.None);

        // Assert
        var exception = await act.Should().ThrowAsync<BusinessException>();
        exception.Which.ErrorCode.Should().Be(ErrorCodes.Conflict);
    }

    [Fact]
    public async Task GivenStaleRowVersion_WhenUpdate_ThenReturnsConflict()
    {
        // Arrange
        using var database = new SqliteTestDatabase();
        using var context = database.CreateContext(tenantId: 500, userId: 11);

        var announcement = CreateDraftAnnouncement(tenantId: 500, id: 9005);
        announcement.RowVersion = new byte[] { 1 };
        context.TenantAnnouncements.Add(announcement);
        await context.SaveChangesAsync();

        await context.Database.ExecuteSqlRawAsync(
            "UPDATE tenant_announcements SET \"RowVersion\" = {0} WHERE \"Id\" = {1}",
            new byte[] { 9 }, announcement.Id);
        context.ChangeTracker.Clear();

        var repository = new EfTenantAnnouncementRepository(context);
        var handler = new UpdateTenantAnnouncementCommandHandler(repository);

        var command = new UpdateTenantAnnouncementCommand
        {
            TenantId = 500,
            AnnouncementId = announcement.Id,
            Title = "并发更新",
            Content = "内容",
            TargetType = "ALL_TENANTS",
            RowVersion = new byte[] { 1 }
        };

        // Act
        Func<Task> act = async () => await handler.Handle(command, CancellationToken.None);

        // Assert
        var exception = await act.Should().ThrowAsync<BusinessException>();
        exception.Which.ErrorCode.Should().Be(ErrorCodes.Conflict);
    }

    private static TenantAnnouncement CreateDraftAnnouncement(long tenantId, long id)
        => new()
        {
            Id = id,
            TenantId = tenantId,
            Title = "公告",
            Content = "内容",
            AnnouncementType = TenantAnnouncementType.System,
            Priority = 1,
            EffectiveFrom = DateTime.UtcNow.AddMinutes(-10),
            EffectiveTo = DateTime.UtcNow.AddMinutes(30),
            PublisherScope = PublisherScope.Tenant,
            Status = AnnouncementStatus.Draft,
            TargetType = "ALL_TENANTS",
            TargetParameters = null,
            RowVersion = new byte[] { 1 }
        };
}
