using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using CamBooth.App.Core.Logging;
using CamBooth.App.Features.Camera;

namespace CamBooth.App.Features.LiveView;

public partial class LiveViewPage : IDisposable
{
    private readonly CameraService _cameraService;
    private readonly Logger _logger;
    private readonly ImageBrush _bgBrush = new();
    private int _targetPixelWidth;
    private readonly object _frameLock = new();
    private byte[]? _latestFrame;
    private int _workerRunning;


    public LiveViewPage(Logger logger, CameraService cameraService)
    {
        _logger = logger;
        _cameraService = cameraService;

        InitializeComponent();

        _bgBrush.Stretch = Stretch.UniformToFill;
        _bgBrush.AlignmentX = AlignmentX.Center;
        _bgBrush.AlignmentY = AlignmentY.Center;
        LVCanvas.Background = _bgBrush;
        if (LVCanvas.ActualWidth > 0)
            _targetPixelWidth = (int)Math.Round(LVCanvas.ActualWidth);
        RenderOptions.SetBitmapScalingMode(LVCanvas, BitmapScalingMode.LowQuality);

        var transformGroup = new TransformGroup();
        transformGroup.Children.Add(new ScaleTransform { ScaleX = -1, ScaleY = 1 });
        transformGroup.Children.Add(new TranslateTransform { X = 1, Y = 0 });
        LVCanvas.RenderTransform = transformGroup;
        LVCanvas.RenderTransformOrigin = new Point(0.5, 0.5);
        LVCanvas.SizeChanged += (_, e) =>
        {
            var width = (int)Math.Round(e.NewSize.Width);
            if (width > 0) _targetPixelWidth = width;
        };

        try
        {
            cameraService.ConnectCamera();
            cameraService.LiveViewUpdated += OnLiveViewUpdated;
            _logger.Info("LiveViewPage initialized successfully");
        }
        catch (Exception ex)
        {
            _logger.Error($"Failed to initialize LiveViewPage: {ex.Message}");
            throw;
        }
    }


    public void Dispose()
    {
        _cameraService.LiveViewUpdated -= OnLiveViewUpdated;
        _cameraService.Dispose();
    }


    private void OnLiveViewUpdated(Stream img)
    {
        if (img == null) return;
        if (!img.CanRead) return;

        try
        {
            if (img.CanSeek) img.Position = 0;
            using var ms = new MemoryStream();
            img.CopyTo(ms);
            lock (_frameLock)
            {
                _latestFrame = ms.ToArray();
            }
            TryStartWorker();
        }
        catch (Exception ex)
        {
            _logger.Error($"LiveView update failed: {ex}");
        }
    }

    private void TryStartWorker()
    {
        if (Interlocked.Exchange(ref _workerRunning, 1) == 1)
            return;

        _ = Task.Run(ProcessFramesAsync);
    }

    private async Task ProcessFramesAsync()
    {
        const int targetFps = 60;
        const double minFrameMs = 1000d / targetFps;

        try
        {
            while (true)
            {
                var sw = Stopwatch.StartNew();
                byte[]? frame;
                lock (_frameLock)
                {
                    frame = _latestFrame;
                    _latestFrame = null;
                }

                if (frame == null)
                    break;

                BitmapImage? evfImage = null;
                try
                {
                    using var ms = new MemoryStream(frame);
                    evfImage = new BitmapImage();
                    evfImage.BeginInit();
                    evfImage.StreamSource = ms;
                    evfImage.CacheOption = BitmapCacheOption.OnLoad;
                    if (_targetPixelWidth > 0)
                        evfImage.DecodePixelWidth = _targetPixelWidth;
                    evfImage.EndInit();
                    evfImage.Freeze();
                }
                catch (Exception ex)
                {
                    _logger.Error($"LiveView decode failed: {ex}");
                }

                if (evfImage != null)
                {
                    await Application.Current.Dispatcher.InvokeAsync(
                        () => _bgBrush.ImageSource = evfImage,
                        DispatcherPriority.Render);
                }

                sw.Stop();
                var remainingMs = minFrameMs - sw.Elapsed.TotalMilliseconds;
                if (remainingMs > 0)
                    await Task.Delay((int)remainingMs);
            }
        }
        finally
        {
            Interlocked.Exchange(ref _workerRunning, 0);
            lock (_frameLock)
            {
                if (_latestFrame != null)
                    TryStartWorker();
            }
        }
    }
}
