using System.Threading.Channels;
using ErsatzTV.Core;
using ErsatzTV.Core.Interfaces.Locking;
using ErsatzTV.Core.Interfaces.Plex;

namespace ErsatzTV.Application.Plex;

public class TryCompletePlexPinFlowHandler : IRequestHandler<TryCompletePlexPinFlow, Either<BaseError, bool>>
{
    private readonly ChannelWriter<IPlexBackgroundServiceRequest> _channel;
    private readonly IEntityLocker _entityLocker;
    private readonly IPlexTvApiClient _plexTvApiClient;

    public TryCompletePlexPinFlowHandler(
        IPlexTvApiClient plexTvApiClient,
        ChannelWriter<IPlexBackgroundServiceRequest> channel,
        IEntityLocker entityLocker)
    {
        _plexTvApiClient = plexTvApiClient;
        _channel = channel;
        _entityLocker = entityLocker;
    }

    public async Task<Either<BaseError, bool>>
        Handle(TryCompletePlexPinFlow request, CancellationToken cancellationToken)
    {
        // Lock-release discipline (#202 §C1.6 / §C6): the Plex lock this pin flow holds is released
        // ONLY on non-handoff exits — the 2-minute timeout (Task.Delay throws
        // OperationCanceledException), a poll exception, a failed enqueue, or the (effectively dead)
        // return-false at loop entry. On SUCCESS the lock is HANDED OFF to SynchronizePlexMediaSources,
        // whose handler is the sole releaser after server discovery (SynchronizePlexMediaSourcesHandler).
        // This is deliberately NOT an unconditional finally: a blanket release here would double-release
        // AND release before discovery, re-opening the finding-5 race (an empty server list reading as
        // success). Contrast the terminal SignOutOfPlexHandler, which DOES use finally (it has no handoff).
        using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
        using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken);
        CancellationToken token = linkedTokenSource.Token;
        try
        {
            while (!token.IsCancellationRequested)
            {
                bool result = await _plexTvApiClient.TryCompletePinFlow(request.AuthPin);
                if (result)
                {
                    // hand the lock off to the sync handler — do NOT release on this success path
                    await _channel.WriteAsync(new SynchronizePlexMediaSources(), token);
                    return true;
                }

                await Task.Delay(TimeSpan.FromSeconds(1), token);
            }

            // effectively unreachable (Task.Delay throws on cancellation before the loop condition is
            // re-evaluated) but if the flow ever ends here it abandoned without auth → release
            _entityLocker.UnlockPlex();
            return false;
        }
        catch (Exception)
        {
            // non-handoff exit: timeout-throw, poll exception, or failed enqueue — release the lock so an
            // abandoned flow does not wedge Plex locked, then rethrow (PlexService logs it as before)
            _entityLocker.UnlockPlex();
            throw;
        }
    }
}
