using Microsoft.EntityFrameworkCore;
using NSubstitute;
using TeleWave.Application.Broadcast;
using TeleWave.Application.Broadcast.Bumpers;
using TeleWave.Application.Broadcast.GetChannel;
using TeleWave.Application.Broadcast.GetSchedule;
using TeleWave.Application.Broadcast.ListChannels;
using TeleWave.Application.Common.Interfaces;
using TeleWave.Application.Library.DeleteShow;
using TeleWave.Application.Library.GetShow;
using TeleWave.Application.Programming.Groups;
using TeleWave.Application.Tests.Support;
using TeleWave.Domain.Broadcast;
using TeleWave.Domain.Library;
using TeleWave.Domain.Media;
using Xunit;

namespace TeleWave.Application.Tests.Broadcast;

public class QueryHandlersTests
{
    private static readonly DateTimeOffset T0 = new(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);

    private static GroupMembershipCleaner GroupCleaner(IAppDbContext db) =>
        new(db, GroupServices.Stats(db));

    [Fact]
    public async Task GetChannel_UnknownId_ReturnsNotFound()
    {
        var fixture = new TestDb();
        await using var db = fixture.New();
        var result = await new GetChannelQueryHandler(db).Handle(
            new GetChannelQuery(Guid.NewGuid()),
            CancellationToken.None
        );
        Assert.False(result.IsSuccess);
    }

    [Fact]
    public async Task ListChannels_ReturnsSummaries()
    {
        var fixture = new TestDb();
        await using (var seed = fixture.New())
        {
            seed.Channels.Add(Channel.Create("A", "a", T0));
            seed.Channels.Add(Channel.Create("B", "b", T0));
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var result = await new ListChannelsQueryHandler(db).Handle(
            new ListChannelsQuery(),
            CancellationToken.None
        );
        Assert.Equal(2, result.Count);
    }

    [Fact]
    public async Task GetChannelSchedule_ReturnsEntriesWithBumperLabel()
    {
        var fixture = new TestDb();
        var channel = Channel.Create("c", "c", T0);
        var show = Show.Create("Show A", ShowKind.Series);
        var variant = channel.BumperTemplates[0].Variants[0];
        var asset = MediaAsset.Register("Show.A.S01E01.mkv", ".mkv", MediaSource.Upload);
        var program = ScheduleEntry.Program(
            channel.Id,
            asset.Id,
            T0,
            T0.AddMinutes(20),
            show.Id,
            0
        );
        var bumper = ScheduleEntry.Bumper(
            channel.Id,
            Guid.NewGuid(),
            T0.AddMinutes(20),
            T0.AddMinutes(20).AddSeconds(8),
            show.Id,
            variant.Id
        );
        await using (var seed = fixture.New())
        {
            seed.Shows.Add(show);
            seed.Channels.Add(channel);
            seed.MediaAssets.Add(asset);
            seed.ScheduleEntries.Add(program);
            seed.ScheduleEntries.Add(bumper);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var result = await new GetChannelScheduleQueryHandler(db).Handle(
            new GetChannelScheduleQuery(channel.Id, T0.AddMinutes(-5), T0.AddHours(1)),
            CancellationToken.None
        );

        Assert.True(result.IsSuccess);
        Assert.Equal(2, result.Value.Count);
        var bumperDto = result.Value.Single(e => e.Kind == ScheduleEntryKind.Bumper);
        Assert.NotNull(bumperDto.BumperName);
    }

    [Fact]
    public async Task GetShow_ReturnsDtoOrNotFound()
    {
        var fixture = new TestDb();
        var show = Show.Create("A", ShowKind.Series);
        await using (var seed = fixture.New())
        {
            seed.Shows.Add(show);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var ok = await new GetShowQueryHandler(db).Handle(
            new GetShowQuery(show.Id),
            CancellationToken.None
        );
        Assert.True(ok.IsSuccess);

        var missing = await new GetShowQueryHandler(db).Handle(
            new GetShowQuery(Guid.NewGuid()),
            CancellationToken.None
        );
        Assert.False(missing.IsSuccess);
    }

    [Fact]
    public async Task DeleteShow_RemovesOrNotFound()
    {
        var fixture = new TestDb();
        var show = Show.Create("A", ShowKind.Series);
        await using (var seed = fixture.New())
        {
            seed.Shows.Add(show);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        await using var db = fixture.New();
        var missing = await new DeleteShowCommandHandler(db, GroupCleaner(db)).Handle(
            new DeleteShowCommand(Guid.NewGuid()),
            CancellationToken.None
        );
        Assert.False(missing.IsSuccess);

        var ok = await new DeleteShowCommandHandler(db, GroupCleaner(db)).Handle(
            new DeleteShowCommand(show.Id),
            CancellationToken.None
        );
        Assert.True(ok.IsSuccess);
    }

    [Fact]
    public async Task AddAndRemoveBumperTemplate_WorkThroughStorage()
    {
        var fixture = new TestDb();
        var channel = Channel.Create("c", "c", T0);
        await using (var seed = fixture.New())
        {
            seed.Channels.Add(channel);
            await seed.SaveChangesAsync(CancellationToken.None);
        }

        Guid templateId;
        await using (var db = fixture.New())
        {
            var added = await new AddBumperTemplateCommandHandler(db).Handle(
                new AddBumperTemplateCommand(channel.Id, ""),
                CancellationToken.None
            );
            Assert.True(added.IsSuccess);
            templateId = added.Value;
            await db.SaveChangesAsync(CancellationToken.None);
        }

        var storage = Substitute.For<IBumperTemplateStorage>();
        await using (var db = fixture.New())
        {
            var removed = await new RemoveBumperTemplateCommandHandler(db, storage).Handle(
                new RemoveBumperTemplateCommand(channel.Id, templateId),
                CancellationToken.None
            );
            Assert.True(removed.IsSuccess);
        }
        await storage.Received(1).DeleteTemplateAsync(templateId, Arg.Any<CancellationToken>());

        // дефолтный блок удалить нельзя
        await using (var db = fixture.New())
        {
            var def = await db
                .Channels.Include(c => c.BumperTemplates)
                .FirstAsync(c => c.Id == channel.Id);
            var result = await new RemoveBumperTemplateCommandHandler(db, storage).Handle(
                new RemoveBumperTemplateCommand(channel.Id, def.BumperTemplates[0].Id),
                CancellationToken.None
            );
            Assert.Equal(ChannelErrors.CannotRemoveDefaultBumperTemplate, result.Error);
        }
    }
}
