using System.IO;
using System.Windows;
using System.Windows.Interop;
using Microsoft.Extensions.Logging;
using DragonISO.App.Services;
using DragonISO.App.ViewModels;
using DragonISO.Engine.Controller;
using DragonISO.Engine.Interop;
using DragonISO.Engine.NdiInterop;
using DragonISO.Engine.Persistence;
using DragonISO.Engine.Pipeline;

namespace DragonISO.App;

// Linear bootstrap steps that OnStartup walks through, extracted so the
// main file reads as a wiring pipeline rather than a single 200-line
// procedure. Each method here either does its own work or returns a
// signal (bool / nullable) so OnStartup can bail early on failure.
public partial class App
{
    /// <summary>
    /// Acquire the per-user named mutex that gates a single Dragon-ISO
    /// instance per Windows user. Two Dragon-ISOs on the same machine for
    /// the same user race over the NDI finder, the NDI senders, and
    /// %APPDATA%\Dragon-ISO\config.json â€” none of those are safe to share.
    ///
    /// On loss: broadcast the bring-to-front message to wake the existing
    /// instance and signal the caller to <see cref="Application.Shutdown(int)"/>
    /// silently. On win: install the message-pump filter so subsequent
    /// duplicate launches can surface us.
    /// </summary>
    /// <returns>true if this is the first instance; false if we should exit.</returns>
    private bool TryAcquireSingleInstance()
    {
        _singleInstanceMutex = new System.Threading.Mutex(initiallyOwned: true, SingleInstanceMutexName, out var createdNew);
        _ownsSingleInstanceMutex = createdNew;
        if (!createdNew)
        {
            var bringToFront = RegisterWindowMessageW("WildDragon.DragonISO.BringToFront");
            if (bringToFront != 0)
                SendNotifyMessageW(HWND_BROADCAST, bringToFront, IntPtr.Zero, IntPtr.Zero);
            return false;
        }

        // We're the first instance. Install the message-pump filter so a
        // *subsequent* launch that broadcasts our bring-to-front message
        // surfaces our window. Hold the delegate in a field so OnExit can
        // unsubscribe cleanly (ComponentDispatcher is process-static).
        var bringToFrontMsg = RegisterWindowMessageW("WildDragon.DragonISO.BringToFront");
        _bringToFrontHandler = (ref MSG msg, ref bool handled) =>
        {
            if (msg.message == (int)bringToFrontMsg && MainWindow is not null)
            {
                if (MainWindow.WindowState == WindowState.Minimized) MainWindow.WindowState = WindowState.Normal;
                MainWindow.Activate();
                MainWindow.Topmost = true;
                MainWindow.Topmost = false;
                handled = true;
            }
        };
        ComponentDispatcher.ThreadFilterMessage += _bringToFrontHandler;
        return true;
    }

    /// <summary>
    /// Initialize the NDI interop layer. On failure (most commonly: NDI
    /// Runtime isn't installed), show the operator a "go to ndi.video/tools"
    /// dialog and signal a clean shutdown. The boolean return is checked
    /// by OnStartup so we don't continue past a broken NDI host.
    /// </summary>
    /// <returns>true on success; false if OnStartup should Shutdown(2).</returns>
    private bool TryBootstrapNdiInterop()
    {
        if (_loggerFactory is null) return false;
        try
        {
            _interop = new NdiInteropPInvoke(_loggerFactory.CreateLogger<NdiInteropPInvoke>());
            return true;
        }
        catch (Exception ex)
        {
            MessageBox.Show(
                "Dragon-ISO could not initialize the NDI runtime.\n\n" +
                "Install the NDI Runtime from https://ndi.video/tools/ and try again.\n\n" +
                "Details: " + ex.Message,
                "Dragon-ISO â€” NDI runtime missing",
                MessageBoxButton.OK,
                MessageBoxImage.Error);
            return false;
        }
    }

    /// <summary>
    /// Wire the engine: configstore, NDI runtime probe, frame scaler,
    /// pipeline factory, IsoController. Doesn't start the engine â€” that's
    /// MainViewModel.InitializeAsync's job.
    /// </summary>
    private void BootstrapEngine()
    {
        if (_loggerFactory is null || _interop is null) return;

        var configPath = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            "Dragon-ISO", "config.json");
        var configStore = new ConfigStore(configPath, _loggerFactory.CreateLogger<ConfigStore>());

        var probe = new NdiRuntimeProbe(_interop, NdiVersion.ExpectedRuntimeVersionPrefix);
        var scaler = new ManagedNearestNeighborFrameScaler();

        var loggerFactoryRef = _loggerFactory;
        var interopRef = _interop;
        IsoPipeline PipelineFactory(IsoPipelineConfig config)
        {
            var clock = new PeriodicTimerFrameClock(config.Settings.FramerateHz);
            return new IsoPipeline(
                config, interopRef, scaler, clock,
                ExponentialBackoff.Default,
                (delay, ct) => Task.Delay(delay, ct),
                loggerFactoryRef);
        }

        _controller = new IsoController(
            _interop, PipelineFactory, configStore, probe, _loggerFactory);
    }

    /// <summary>
    /// Construct the view-model, the main window, and show it. After this
    /// returns, <see cref="Application.MainWindow"/> is non-null and the
    /// window is on screen.
    /// </summary>
    private MainWindow ConstructAndShowMainWindow()
    {
        _viewModel = new MainViewModel(_controller!, Dispatcher);
        var window = new MainWindow(_viewModel);
        window.Show();
        MainWindow = window;
        return window;
    }

    /// <summary>
    /// REST + WebSocket control surface for Stream Deck / Companion and
    /// the OSC bridge. Created always; only Started if the operator had
    /// the toggle on in the previous session (the settings VM's setter
    /// handles the in-session flip path). Failures log + toast â€” we don't
    /// want a port-bind error to block app start.
    /// </summary>
    private void BootstrapControlSurfaceServices()
    {
        if (_controller is null || _viewModel is null || _loggerFactory is null) return;

        _controlSurface = new ControlSurfaceServer(
            _controller,
            () => _viewModel,
            _loggerFactory.CreateLogger<ControlSurfaceServer>());
        _oscBridge = new OscBridge(
            _controller,
            () => _viewModel,
            _loggerFactory.CreateLogger<OscBridge>());

        if (_viewModel.Settings.ControlSurfaceEnabled)
        {
            try
            {
                _controlSurface.Start(
                    _viewModel.Settings.ControlSurfacePort,
                    _viewModel.Settings.ControlSurfaceLanReachable);
            }
            catch (Exception ex)
            {
                _loggerFactory.CreateLogger<App>().LogWarning(ex,
                    "Control surface auto-start failed; operator can retry via Settings.");
            }
        }
    }

    /// <summary>
    /// Tray-icon host. Hosting from App (not MainWindow) ensures icon
    /// lifetime matches the process, so the icon stays visible during a
    /// minimize-to-tray (when MainWindow is hidden).
    /// </summary>
    private void BootstrapTrayIcon(MainWindow window)
    {
        if (_viewModel is null) return;
        _trayIcon = new TrayIconHost(window)
        {
            Enabled = _viewModel.Settings.MinimizeToTray,
        };
    }

    /// <summary>
    /// First-launch onboarding dialog. Shown AFTER MainWindow so it has
    /// a sensible Owner for centering + z-order. Suppressed forever once
    /// the user dismisses with the checkbox checked.
    /// </summary>
    private static void TryShowOnboarding(MainWindow window)
    {
        if (!OnboardingWindow.ShouldShow()) return;
        try
        {
            var onboarding = new OnboardingWindow { Owner = window };
            onboarding.ShowDialog();
        }
        catch
        {
            // Defensive: an onboarding-dialog failure should never block startup.
        }
    }

    /// <summary>
    /// Auto-launch Teams in the background if the operator opted in.
    /// Combined with AutoHideTeamsWindows this gives the "I only see
    /// Dragon-ISO" experience. Fire-and-forget â€” a slow Teams launch must
    /// not delay Dragon-ISO's own window from appearing.
    /// </summary>
    private void TryAutoLaunchTeams(ILogger logger)
    {
        if (_viewModel is null) return;
        var settings = _viewModel.Settings;

        if (settings.LaunchTeamsOnStartup && !TeamsLauncher.IsRunning())
        {
            _ = Task.Run(() =>
            {
                try
                {
                    if (TeamsLauncher.TryLaunch(out var launchError))
                    {
                        if (settings.AutoHideTeamsWindows)
                            _ = TeamsLauncher.AutoHideAfterLaunchAsync();
                    }
                    else
                    {
                        logger.LogWarning("Auto-launch Teams on startup failed: {Error}", launchError);
                    }
                }
                catch (Exception ex)
                {
                    logger.LogWarning(ex, "Auto-launch Teams on startup threw");
                }
            });
        }
        else if (settings.AutoHideTeamsWindows && TeamsLauncher.IsRunning())
        {
            // Teams is already up from a previous session. If auto-hide is
            // on, hide it now so the operator's "I only see Dragon-ISO" rule
            // applies even when Teams was launched externally.
            _ = TeamsLauncher.AutoHideAfterLaunchAsync();
        }
    }
}
