using Microsoft.EntityFrameworkCore;
using NSubstitute;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Maintenance.DeleteShowMedia;
using TeleWave.Application.Media.Register;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using Xunit;

namespace TeleWave.Integration.Tests;

[Collection("postgres")]
public sealed class TransactionIntegrationTests(PostgresFixture fixture)
{
    [SkippableFact]
    public async Task RegisterMediaAsset_RollsBack_WhenFilePromoteFails()
    {
        Skip.IfNot(fixture.Available, "Docker недоступен");
        var fileName = $"clip-{Guid.NewGuid():N}.mkv";
        var storage = Substitute.For<IMediaStorage>();
        storage
            .PromoteToOriginalAsync(
                Arg.Any<MediaSource>(),
                Arg.Any<string>(),
                Arg.Any<Guid>(),
                Arg.Any<string>(),
                Arg.Any<CancellationToken>()
            )
            .Returns(Task.FromException(new IOException("disk full")));

        await using var db = fixture.CreateContext();
        var handler = new RegisterMediaAssetCommandHandler(db, storage);

        await Assert.ThrowsAsync<IOException>(() =>
            handler.Handle(
                new RegisterMediaAssetCommand(fileName, MediaSource.Upload, fileName),
                CancellationToken.None
            )
        );

        // Перенос файла упал — регистрация должна откатиться, чтобы не осталось orphan-строки.
        await using var verify = fixture.CreateContext();
        var exists = await verify.MediaAssets.AnyAsync(a => a.OriginalFileName == fileName);
        Assert.False(exists);
    }

    [SkippableFact]
    public async Task DeleteShowMedia_AtomicallyRemovesEntriesAssetsAndEpisodes()
    {
        Skip.IfNot(fixture.Available, "Docker недоступен");
        var channel = Channel.Create("c", $"c-{Guid.NewGuid():N}", DateTimeOffset.UnixEpoch);
        var show = Show.Create("Show", ShowKind.Series);
        var asset = MediaAsset.Register("ep.mkv", ".mkv", MediaSource.Upload);
        asset.MarkReady(
            new MediaReadyInfo(
                TimeSpan.FromMinutes(20),
                2,
                600,
                1920,
                1080,
                "h264",
                "aac",
                "assets/x"
            )
        );
        show.AddEpisode(asset.Id);
        var entry = ScheduleEntry.Program(
            channel.Id,
            asset.Id,
            DateTimeOffset.UnixEpoch,
            DateTimeOffset.UnixEpoch.AddMinutes(20),
            show.Id,
            0
        );

        await using (var seed = fixture.CreateContext())
        {
            seed.Channels.Add(channel);
            seed.Shows.Add(show);
            seed.MediaAssets.Add(asset);
            seed.ScheduleEntries.Add(entry);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        var storage = Substitute.For<IMediaStorage>();
        storage
            .DeleteAssetArtifactsAsync(
                Arg.Any<Guid>(),
                Arg.Any<string>(),
                Arg.Any<CancellationToken>()
            )
            .Returns(Task.CompletedTask);

        await using (var db = fixture.CreateContext())
        {
            var result = await new DeleteShowMediaCommandHandler(db, storage).Handle(
                new DeleteShowMediaCommand(show.Id),
                CancellationToken.None
            );
            Assert.True(result.IsSuccess);
        }

        await using var verify = fixture.CreateContext();
        Assert.False(await verify.MediaAssets.AnyAsync(a => a.Id == asset.Id));
        Assert.False(await verify.ScheduleEntries.AnyAsync(e => e.MediaAssetId == asset.Id));
        var reloaded = await verify.Shows.Include(s => s.Episodes).FirstAsync(s => s.Id == show.Id);
        Assert.Empty(reloaded.Episodes);
        await storage
            .Received()
            .DeleteAssetArtifactsAsync(asset.Id, ".mkv", Arg.Any<CancellationToken>());
    }
}
