using Xunit;
using ytLive.Services;

namespace ytLive.Tests;

public class CameraManagerTests
{
    private sealed class FakeFrameSource : ICameraFrameSource
    {
        private readonly List<string> _started;
        private readonly List<string> _stopped;

        public string DeviceId { get; }
        public event Action<VideoFrame>? FrameAvailable;

        public FakeFrameSource(string deviceId, List<string> started, List<string> stopped)
        {
            DeviceId = deviceId;
            _started = started;
            _stopped = stopped;
        }

        public Task StartAsync()
        {
            _started.Add(DeviceId);
            return Task.CompletedTask;
        }

        public Task StopAsync()
        {
            _stopped.Add(DeviceId);
            return Task.CompletedTask;
        }

        public void Pump(VideoFrame frame) => FrameAvailable?.Invoke(frame);
    }

    private sealed class FailingFrameSource : ICameraFrameSource
    {
        public string DeviceId { get; }
        public event Action<VideoFrame>? FrameAvailable;
        public bool Stopped;

        public FailingFrameSource(string deviceId) => DeviceId = deviceId;

        public Task StartAsync() => throw new InvalidOperationException("camera in use");

        public Task StopAsync()
        {
            Stopped = true;
            return Task.CompletedTask;
        }

        public void Pump(VideoFrame frame) => FrameAvailable?.Invoke(frame);
    }

    private static CameraManager CreateManager(List<string> started, List<string> stopped)
        => new(new FakeEnumerator(), id => new FakeFrameSource(id, started, stopped));

    private sealed class FakeEnumerator : ICameraEnumerator
    {
        public Task<IReadOnlyList<CameraDeviceInfo>> GetCamerasAsync()
            => Task.FromResult<IReadOnlyList<CameraDeviceInfo>>(new[]
            {
                new CameraDeviceInfo("dev1", "Logitech C920"),
            });
    }

    [Fact]
    public async Task Refcount_TwoAcquires_OneCaptureUntilLastRelease()
    {
        var started = new List<string>();
        var stopped = new List<string>();
        var manager = CreateManager(started, stopped);

        Assert.True(await manager.AcquireAsync("dev1"));
        Assert.True(await manager.AcquireAsync("dev1"));
        Assert.Single(started);

        await manager.ReleaseAsync("dev1");
        Assert.Empty(stopped);

        await manager.ReleaseAsync("dev1");
        Assert.Single(stopped);
        Assert.Null(manager.GetLatestFrame("dev1"));
    }

    [Fact]
    public async Task FramePump_UpdatesLatestFrame()
    {
        var started = new List<string>();
        var stopped = new List<string>();
        FakeFrameSource? captured = null;
        var manager = new CameraManager(
            new FakeEnumerator(),
            id => captured = new FakeFrameSource(id, started, stopped));

        await manager.AcquireAsync("dev1");
        var frame = new VideoFrame(2, 2, new byte[16]);
        captured!.Pump(frame);

        Assert.Same(frame, manager.GetLatestFrame("dev1"));
    }

    [Fact]
    public async Task Acquire_FailingCamera_ReturnsFalseAndStops()
    {
        var manager = new CameraManager(new FakeEnumerator(), id => new FailingFrameSource(id));
        string? failedDevice = null;
        manager.CameraFailed += (device, _) => failedDevice = device;

        Assert.False(await manager.AcquireAsync("dev1"));
        Assert.Equal("dev1", failedDevice);
    }

    [Fact]
    public async Task Acquire_EmptyDeviceId_ReturnsFalse()
    {
        var manager = CreateManager(new List<string>(), new List<string>());
        Assert.False(await manager.AcquireAsync(" "));
    }
}
