using System.Threading.Channels;
using ErsatzTV.Application.Playouts;
using ErsatzTV.Core;
using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace ErsatzTV.Application.Scheduling;

public class UpdateDefaultDecoHandler(
    IDbContextFactory<TvContext> dbContextFactory,
    ChannelWriter<IBackgroundServiceRequest> channel)
    : IRequestHandler<UpdateDefaultDeco, Option<BaseError>>
{
    public async Task<Option<BaseError>> Handle(UpdateDefaultDeco request, CancellationToken cancellationToken)
    {
        try
        {
            await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);

            int updated = await dbContext.Playouts
                .Where(p => p.Id == request.PlayoutId)
                .ExecuteUpdateAsync(
                    u => u
                        .SetProperty(p => p.DecoId, p => request.DecoId)
                        // Bump the shared Playout.Version so any open alternate-schedule/template editor tab
                        // (which round-trips this same token) detects the deco change and reloads (#253 §7a).
                        // Bulk ExecuteUpdate bypasses change-tracking, so bump via SetProperty, not Version++.
                        .SetProperty(p => p.Version, p => p.Version + 1),
                    cancellationToken);

            // Changing a playout's default deco only takes effect on a Reset build (deco content is applied
            // during Reset, and BlockKey has no deco dimension to self-heal) — same stale-until-Reset class
            // as #251. Enqueue a Reset for the affected playout, but only if a row was actually updated: a
            // request for a nonexistent playout must not queue a build for an id that isn't there (keeps the
            // enqueued set equal to the affected set). CancellationToken.None so the rebuild isn't dropped
            // after the assignment has already been persisted.
            if (updated > 0)
            {
                await channel.WriteAsync(
                    new BuildPlayout(request.PlayoutId, PlayoutBuildMode.Reset),
                    CancellationToken.None);
            }

            return Option<BaseError>.None;
        }
        catch (Exception ex)
        {
            return BaseError.New(ex.ToString());
        }
    }
}
