using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.UI.Dispatching;
using TeamsISO.Engine.Controller;
using TeamsISO.Engine.Domain;

namespace TeamsISO.App.WinUI.ViewModels;

/// <summary>
/// Top-level view-model for the WinUI 3 host. Subscribes to
/// <see cref="IIsoController"/>'s observables and marshals updates onto
/// the WinUI 3 dispatcher queue (the WinUI equivalent of WPF Dispatcher).
///
/// Slim version of the WPF host's MainViewModel — drops the WPF-specific
/// ICollectionView filter/sort, preset auto-apply, snapshot dialog, and
/// help/notes window orchestration. Those land in subsequent commits.
/// </summary>
public sealed class MainViewModel : ObservableObject, IDisposable
{
    private readonly IIsoController _controller;
    private readonly DispatcherQueue _dispatcher;
    private readonly DispatcherQueueTimer _statsTimer;
    private readonly IDisposable _participantsSub;
    private readonly IDisposable _alertsSub;
    private readonly Dictionary<Guid, ParticipantViewModel> _byId = new();
    private string _statusText = "Starting…";
    private bool _isSessionActive;
    private DateTimeOffset? _sessionStartedAt;
    private string _sessionElapsed = "00:00:00";
    private int _activeRecordingCount;
    private string _participantCountText = "0";

    public ObservableCollection<ParticipantViewModel> Participants { get; } = new();

    public string StatusText
    {
        get => _statusText;
        private set => SetField(ref _statusText, value);
    }

    public string ParticipantCountText
    {
        get => _participantCountText;
        private set => SetField(ref _participantCountText, value);
    }

    public bool IsSessionActive
    {
        get => _isSessionActive;
        private set => SetField(ref _isSessionActive, value);
    }

    public string SessionElapsed
    {
        get => _sessionElapsed;
        private set => SetField(ref _sessionElapsed, value);
    }

    public int ActiveRecordingCount
    {
        get => _activeRecordingCount;
        private set => SetField(ref _activeRecordingCount, value);
    }

    public bool IsRecording => ActiveRecordingCount > 0;

    public AsyncRelayCommand EnableAllOnlineCommand { get; }
    public AsyncRelayCommand StopAllIsosCommand { get; }
    public RelayCommand RefreshDiscoveryCommand { get; }
    public RelayCommand DropRecordingMarkerCommand { get; }
    public RelayCommand<string> ToggleByIndexCommand { get; }

    public MainViewModel(IIsoController controller, DispatcherQueue dispatcher)
    {
        _controller = controller;
        _dispatcher = dispatcher;

        // Subscribe to engine observables. ObserveOn the WinUI dispatcher
        // queue's SynchronizationContext so handlers run on the UI thread
        // and SetField is safe.
        var ctx = new DispatcherQueueSynchronizationContext(_dispatcher);
        _participantsSub = controller.Participants
            .ObserveOn(new SynchronizationContextScheduler(ctx))
            .Subscribe(OnParticipantsChanged);

        _alertsSub = controller.Alerts
            .ObserveOn(new SynchronizationContextScheduler(ctx))
            .Subscribe(alert =>
            {
                StatusText = $"Alert · {alert.Message}";
            });

        // 1 Hz stats tick — pulls live frame counters off the engine and
        // pushes them into per-participant view-models. Runs on the WinUI
        // dispatcher so SetField is safe.
        _statsTimer = _dispatcher.CreateTimer();
        _statsTimer.Interval = TimeSpan.FromSeconds(1);
        _statsTimer.Tick += OnStatsTick;
        _statsTimer.Start();

        EnableAllOnlineCommand = new AsyncRelayCommand(
            EnableAllOnlineAsync,
            () => Participants.Any(p => p.IsOnline && !p.IsEnabled));

        StopAllIsosCommand = new AsyncRelayCommand(
            StopAllIsosAsync,
            () => Participants.Any(p => p.IsEnabled));

        RefreshDiscoveryCommand = new RelayCommand(() =>
        {
            _controller.RefreshDiscovery();
            StatusText = "Refreshing NDI discovery…";
        });

        DropRecordingMarkerCommand = new RelayCommand(() =>
        {
            var label = "Marker @ " + DateTimeOffset.Now.ToString("HH:mm:ss");
            _controller.AddRecordingMarker(label);
            StatusText = $"Marker dropped: {label}";
        });

        ToggleByIndexCommand = new RelayCommand<string>(s =>
        {
            if (!int.TryParse(s, out var idx) || idx < 1 || idx > 9) return;
            var i = 0;
            foreach (var p in Participants)
            {
                if (++i == idx)
                {
                    if (p.ToggleIsoCommand.CanExecute(null))
                        p.ToggleIsoCommand.Execute(null);
                    break;
                }
            }
        });
    }

    /// <summary>
    /// Kick off the engine controller's StartAsync. Awaited by App.OnLaunched
    /// so the participants observable starts firing as soon as discovery's up.
    /// </summary>
    public Task InitializeAsync(CancellationToken ct) => _controller.StartAsync(ct);

    private void OnParticipantsChanged(IReadOnlyList<Participant> snapshot)
    {
        // Reconcile: add new, update existing, remove gone. Identity is by Guid.
        var present = new HashSet<Guid>();
        foreach (var p in snapshot)
        {
            present.Add(p.Id);
            if (_byId.TryGetValue(p.Id, out var vm))
            {
                vm.Update(p);
            }
            else
            {
                vm = new ParticipantViewModel(_controller, p);
                _byId[p.Id] = vm;
                Participants.Add(vm);
            }
        }
        for (var i = Participants.Count - 1; i >= 0; i--)
        {
            var vm = Participants[i];
            if (!present.Contains(vm.Id))
            {
                _byId.Remove(vm.Id);
                Participants.RemoveAt(i);
            }
        }

        ParticipantCountText = Participants.Count.ToString();
        StatusText = Participants.Count == 0
            ? "Waiting for Teams participants…"
            : $"{Participants.Count} participants · {Participants.Count(p => p.IsEnabled)} routing";

        EnableAllOnlineCommand.RaiseCanExecuteChanged();
        StopAllIsosCommand.RaiseCanExecuteChanged();
    }

    private void OnStatsTick(DispatcherQueueTimer sender, object args)
    {
        // Pull stats + update active-speaker highlight.
        foreach (var vm in Participants)
        {
            try
            {
                var stats = _controller.GetStats(vm.Id);
                vm.UpdateStats(stats);
            }
            catch
            {
                // Stats are advisory; transient read failures shouldn't
                // tear down the timer.
            }
        }

        // Active speaker: loudest among the enabled, threshold 0.05 to
        // prevent flicker between near-silent participants.
        ParticipantViewModel? loudest = null;
        double loudestLevel = 0.05;
        foreach (var p in Participants)
        {
            if (!p.IsEnabled) continue;
            if (p.DisplayedAudioLevel > loudestLevel)
            {
                loudest = p;
                loudestLevel = p.DisplayedAudioLevel;
            }
        }
        foreach (var p in Participants)
        {
            var shouldHighlight = ReferenceEquals(p, loudest);
            if (p.IsActiveSpeaker != shouldHighlight)
                p.IsActiveSpeaker = shouldHighlight;
        }

        // Session timer + recording count.
        var enabledCount = Participants.Count(p => p.IsEnabled);
        if (enabledCount > 0 && !IsSessionActive)
        {
            IsSessionActive = true;
            _sessionStartedAt = DateTimeOffset.UtcNow;
        }
        else if (enabledCount == 0 && IsSessionActive)
        {
            IsSessionActive = false;
            _sessionStartedAt = null;
        }
        if (_sessionStartedAt is { } start)
        {
            var elapsed = DateTimeOffset.UtcNow - start;
            SessionElapsed = elapsed.ToString(@"hh\:mm\:ss");
        }

        ActiveRecordingCount = _controller.RecordingEnabled ? enabledCount : 0;
        OnPropertyChanged(nameof(IsRecording));

        EnableAllOnlineCommand.RaiseCanExecuteChanged();
        StopAllIsosCommand.RaiseCanExecuteChanged();
    }

    private async Task EnableAllOnlineAsync()
    {
        foreach (var p in Participants.Where(p => p.IsOnline && !p.IsEnabled).ToArray())
        {
            if (p.ToggleIsoCommand.CanExecute(null))
            {
                p.ToggleIsoCommand.Execute(null);
                await Task.Delay(50); // small gap so we don't hammer the engine
            }
        }
    }

    private async Task StopAllIsosAsync()
    {
        foreach (var p in Participants.Where(p => p.IsEnabled).ToArray())
        {
            try
            {
                await _controller.DisableIsoAsync(p.Id, CancellationToken.None);
            }
            catch
            {
                // Continue with remaining; we'll re-sync on the next observable tick.
            }
        }
    }

    public void Dispose()
    {
        _statsTimer.Stop();
        _participantsSub.Dispose();
        _alertsSub.Dispose();
    }
}
