using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using Microsoft.Extensions.Logging;
using DragonISO.App.ViewModels;
using DragonISO.Engine.Controller;
using DragonISO.Engine.Logging;
using DragonISO.Engine.NdiInterop;

// Application + MessageBox aliases live in GlobalUsings.cs (project-wide).
// Don't redeclare here â€” Roslyn errors with CS1537 on duplicate alias.

namespace DragonISO.App;

// Split across partial files by responsibility:
//   â€¢ App.xaml.cs              â€” class skeleton, OnStartup (the wiring
//                                pipeline that calls into the partials),
//                                OnExit, CLI arg parser.
//   â€¢ App.Bootstrap.cs         â€” the linear setup steps OnStartup walks
//                                (single-instance gate, NDI interop, engine,
//                                main window, control surface, tray icon,
//                                onboarding, Teams auto-launch).
//   â€¢ App.CrashHandlers.cs     â€” AppDomain / Dispatcher / Task exception
//                                handlers + crash dialog + LogDirectory.
//   â€¢ App.UpdateCheckBootstrap.cs â€” the background update-checker
//                                kickoff (24h-throttled).
public partial class App : Application
{
    /// <summary>
    /// Per-user mutex name. Including the username (acting as a SID proxy) ensures two
    /// different Windows users can each run Dragon-ISO on the same machine, while one
    /// user can't spawn duplicate instances that would contend over the NDI runtime
    /// and the shared %APPDATA%\Dragon-ISO\config.json.
    ///
    /// The "Global\" prefix puts the named object in the system-wide namespace
    /// (not session-local or integrity-isolated). This matters because when an
    /// admin user has UAC effectively disabled, launches from different parents
    /// (elevated File Explorer, non-elevated shell, etc.) can land in slightly
    /// different security contexts. A "Local\" mutex was being created in
    /// different views per integrity level on some boxes, letting two Dragon-ISO
    /// instances run concurrently â€” the second's REST surface couldn't bind port
    /// 9755 (already held) and its Serilog file sink couldn't open the daily log
    /// (already held with shared=false), producing a window that looked like
    /// the app but had no engine attached. Global\ closes that gap.
    /// </summary>
    private static readonly string SingleInstanceMutexName =
        $"Global\\WildDragon.DragonISO.SingleInstance.{Environment.UserName}";

    private System.Threading.Mutex? _singleInstanceMutex;
    private bool _ownsSingleInstanceMutex;
    private ThreadMessageEventHandler? _bringToFrontHandler;
    private ILoggerFactory? _loggerFactory;
    private NdiInteropPInvoke? _interop;
    private IsoController? _controller;
    private MainViewModel? _viewModel;
    private DragonISO.App.Services.ControlSurfaceServer? _controlSurface;
    private DragonISO.App.Services.OscBridge? _oscBridge;
    // _diskSpaceWatcher removed â€” only existed to auto-disable recording at low free space.
    private DragonISO.App.Services.TrayIconHost? _trayIcon;

    /// <summary>
    /// REST control surface lifetime. Lives on App so the settings VM can flip
    /// it on/off without us plumbing yet another DI dependency through MainViewModel.
    /// Null between process startup and the OnStartup wire-up, and after OnExit.
    /// </summary>
    internal DragonISO.App.Services.ControlSurfaceServer? ControlSurface => _controlSurface;

    /// <summary>OSC bridge (UDP) lifetime â€” same lifecycle pattern as the REST surface.</summary>
    internal DragonISO.App.Services.OscBridge? OscBridge => _oscBridge;

    /// <summary>Tray-icon host. Exposed so the settings VM can flip the minimize-to-tray toggle.</summary>
    internal DragonISO.App.Services.TrayIconHost? TrayIcon => _trayIcon;

    [DllImport("user32.dll")]
    private static extern uint RegisterWindowMessageW(string lpString);
    [DllImport("user32.dll")]
    private static extern int PostMessageW(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")]
    private static extern int SendNotifyMessageW(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
    private const IntPtr HWND_BROADCAST = -1;

    protected override async void OnStartup(StartupEventArgs e)
    {
        // RAW TRACE â€” captures startup BEFORE Serilog comes up. Helps diagnose
        // launches where the Serilog log stays empty (silent file-sink failure,
        // pre-logger crash, weird parent-spawn environment, etc.). Writes to
        // %LOCALAPPDATA%\Dragon-ISO\startup-trace.log.
        var parentName = "(unknown)";
        try { parentName = TryGetParentProcessName() ?? "(null)"; } catch { }
        StartupTrace.Write($"OnStartup ENTER. exe={Environment.ProcessPath} parent={parentName} args=[{string.Join(' ', e.Args)}]");
        try
        {
            using var id = System.Security.Principal.WindowsIdentity.GetCurrent();
            var pr = new System.Security.Principal.WindowsPrincipal(id);
            StartupTrace.Write($"identity user={id.Name} isAdmin={pr.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator)} integrity-token={id.User}");
        }
        catch (Exception ex) { StartupTrace.Write($"identity probe FAILED: {ex}"); }

        base.OnStartup(e);
        StartupTrace.Write("base.OnStartup returned");

        // De-elevation via runas /trustlevel:0x20000 was tried (commits 191b2c5,
        // 54ee578) on the theory that elevated Dragon-ISO can't discover NDI
        // sources. THAT THEORY WAS WRONG â€” verified 2026-05-16 that elevated
        // Dragon-ISO discovers NDI sources fine. The SAFER-restricted token
        // produced by runas /trustlevel was the ACTUAL cause of every "no
        // participants" report: it breaks .NET 8 WPF startup such that the
        // process appears alive with a window but the managed code never gets
        // past BAML parsing. No logs, no port binds. We now skip the check
        // entirely. The --keep-elevation arg, originally an opt-out, is now
        // accepted but no-op'd (kept to avoid breaking any operator scripts).
        if (Array.IndexOf(e.Args, "--keep-elevation") >= 0)
            StartupTrace.Write("--keep-elevation flag present (no-op now; de-elevation removed)");

        // Crash diagnostics â€” wire the three exception channels WPF leaves open by
        // default to a single handler that logs Fatal to Serilog.
        AppDomain.CurrentDomain.UnhandledException += OnAppDomainUnhandled;
        DispatcherUnhandledException += OnDispatcherUnhandled;
        System.Threading.Tasks.TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
        StartupTrace.Write("crash handlers registered");

        try { DragonISO.App.Services.ThemeManager.Current.Apply(); StartupTrace.Write("ThemeManager.Apply OK"); }
        catch (Exception ex) { StartupTrace.Write($"ThemeManager.Apply THREW: {ex}"); }

        // Single-instance gate. Trace the mutex acquisition.
        bool acquired = false;
        try { acquired = TryAcquireSingleInstance(); } catch (Exception ex) { StartupTrace.Write($"TryAcquireSingleInstance THREW: {ex}"); }
        StartupTrace.Write($"TryAcquireSingleInstance returned: {acquired}");
        if (!acquired)
        {
            StartupTrace.Write("not first instance â€” Shutdown(0)");
            Shutdown(0);
            return;
        }

        try
        {
            StartupTrace.Write("Bootstrap try-block ENTER");
            _loggerFactory = EngineLogging.CreateDefault(LogLevel.Information);
            StartupTrace.Write("EngineLogging.CreateDefault OK");
            var logger = _loggerFactory.CreateLogger<App>();
            logger.LogInformation(
                "DragonISO.App starting up. Build: {Version}. Process: {Pid}.",
                typeof(App).Assembly.GetName().Version,
                Environment.ProcessId);
            StartupTrace.Write("Serilog first write attempted");

            if (!TryBootstrapNdiInterop())
            {
                StartupTrace.Write("TryBootstrapNdiInterop returned false â€” Shutdown(2)");
                Shutdown(2);
                return;
            }
            StartupTrace.Write("TryBootstrapNdiInterop OK");

            BootstrapEngine();
            StartupTrace.Write("BootstrapEngine OK");
            var window = ConstructAndShowMainWindow();
            StartupTrace.Write("ConstructAndShowMainWindow OK (window shown)");
            BootstrapControlSurfaceServices();
            StartupTrace.Write("BootstrapControlSurfaceServices OK");
            BootstrapTrayIcon(window);
            StartupTrace.Write("BootstrapTrayIcon OK");
            TryShowOnboarding(window);
            StartupTrace.Write("TryShowOnboarding returned");

            ApplyCommandLineArgs(e.Args);
            StartupTrace.Write("ApplyCommandLineArgs OK");

            StartupTrace.Write("about to await _viewModel.InitializeAsync");
            await _viewModel!.InitializeAsync(CancellationToken.None);
            StartupTrace.Write("_viewModel.InitializeAsync COMPLETED");

            TryAutoLaunchTeams(logger);
            StartBackgroundUpdateCheck(logger);
            StartupTrace.Write("OnStartup COMPLETE");

            // 5-second post-init participant probe â€” tells us whether discovery
            // is actually producing rows once the engine is up.
            _ = Task.Run(async () =>
            {
                await Task.Delay(5000);
                try
                {
                    var n = await Dispatcher.InvokeAsync(() => _viewModel?.Participants.Count ?? -1);
                    StartupTrace.Write($"+5s after init: vm.Participants.Count={n}");
                }
                catch (Exception ex) { StartupTrace.Write($"+5s probe THREW: {ex.Message}"); }
            });
        }
        catch (Exception ex)
        {
            StartupTrace.Write($"OnStartup CATCH: {ex}");
            try { _loggerFactory?.CreateLogger<App>().LogCritical(ex, "OnStartup failed before main loop"); }
            catch { /* defensive */ }
            MessageBox.Show(
                "Dragon-ISO failed to start.\n\nDetails: " + ex,
                "Dragon-ISO â€” startup error",
                MessageBoxButton.OK,
                MessageBoxImage.Error);
            Shutdown(1);
        }
    }

    // De-elevation helpers (ShouldDeElevate, TryDeElevateAndExit, the
    // Dragon-ISO_RELAUNCHED env var) were removed 2026-05-16. The whole
    // pattern was treating a symptom that wasn't actually the problem
    // (elevation does NOT break NDI Find); the SAFER token produced by
    // runas /trustlevel:0x20000 broke .NET 8 WPF startup itself, so the
    // "fix" was the actual bug. See git log for the dead code, App.xaml.cs
    // commit history around 191b2c5 / 54ee578 / removal.

    /// <summary>
    /// Look up our parent process's image name (without extension). Returns
    /// null if it can't be determined (PID gone, denied, etc.).
    /// </summary>
    private static string? TryGetParentProcessName()
    {
        try
        {
            var pid = Environment.ProcessId;
            using var search = new System.Management.ManagementObjectSearcher(
                $"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId={pid}");
            foreach (var m in search.Get())
            {
                var ppid = Convert.ToInt32(m["ParentProcessId"]);
                using var parent = System.Diagnostics.Process.GetProcessById(ppid);
                return parent.ProcessName;
            }
        }
        catch { /* fall through */ }
        return null;
    }

    // TryDeElevateAndExit removed 2026-05-16 (see comment above ShouldDeElevate).

    /// <summary>
    /// Parse the supported CLI flags. Currently:
    ///   <c>--apply-preset NAME</c> â€” apply the named preset once participants
    ///   populate. Equivalent to running Dragon-ISO and clicking Presets â†’ select â†’
    ///   Apply, but driven from a desktop shortcut.
    /// Unrecognized flags are silently ignored â€” operators using shortcut.lnk
    /// files don't need to fight argument parsers.
    /// </summary>
    private void ApplyCommandLineArgs(string[] args)
    {
        if (_viewModel is null) return;
        for (var i = 0; i < args.Length; i++)
        {
            switch (args[i])
            {
                case "--apply-preset":
                    if (i + 1 < args.Length && !string.IsNullOrEmpty(args[i + 1]))
                    {
                        _viewModel.RequestApplyPresetOnStartup(args[i + 1]);
                        i++; // consume the value
                    }
                    break;
            }
        }
    }

    // Crash handlers (OnAppDomainUnhandled / OnDispatcherUnhandled /
    // OnUnobservedTaskException / TryLogFatal / TryShowCrashDialog / LogDirectory)
    // live in App.CrashHandlers.cs.

    protected override async void OnExit(ExitEventArgs e)
    {
        try
        {
            _trayIcon?.Dispose();
            if (_controlSurface is not null)
                await _controlSurface.DisposeAsync();
            if (_oscBridge is not null)
                await _oscBridge.DisposeAsync();
            _viewModel?.Dispose();
            if (_controller is not null)
                await _controller.DisposeAsync();
            _interop?.Dispose();
            _loggerFactory?.Dispose();
        }
        catch
        {
            // Best-effort shutdown
        }
        finally
        {
            // Unsubscribe the bring-to-front filter so the delegate doesn't outlive
            // the App; ComponentDispatcher is process-static.
            if (_bringToFrontHandler is not null)
            {
                ComponentDispatcher.ThreadFilterMessage -= _bringToFrontHandler;
                _bringToFrontHandler = null;
            }
            // Release the Mutex iff we acquired it. The "lost the race" path above
            // sets _ownsSingleInstanceMutex=false and we skip ReleaseMutex (which
            // would throw ApplicationException on an unowned Mutex).
            try { if (_ownsSingleInstanceMutex) _singleInstanceMutex?.ReleaseMutex(); }
            catch { /* defensive: already-released or invalid handle */ }
            _singleInstanceMutex?.Dispose();
        }
        base.OnExit(e);
    }
}
