# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
##
## PowerShell Invoke-Command -RemoteDebug Tests
##

if ($IsWindows) {
    $typeDef = @'
    using System;
    using System.Globalization;
    using System.Management.Automation;
    using System.Management.Automation.Runspaces;
    using System.Management.Automation.Host;

    namespace TestRunner
    {
        public class DummyHost : PSHost, IHostSupportsInteractiveSession
        {
            public Runspace _runspace;
            private Guid _instanceId = Guid.NewGuid();

            public override CultureInfo CurrentCulture
            {
                get { return CultureInfo.CurrentCulture; }
            }
            public override CultureInfo CurrentUICulture
            {
                get { return CultureInfo.CurrentUICulture; }
            }
            public override Guid InstanceId
            {
                get { return _instanceId; }
            }
            public override string Name
            {
                get { return "DummyTestHost"; }
            }
            public override PSHostUserInterface UI
            {
                get { return null; }
            }
            public override Version Version
            {
                get { return new Version(1, 0); }
            }
            public override void EnterNestedPrompt() { }
            public override void ExitNestedPrompt() { }
            public override void NotifyBeginApplication() { }
            public override void NotifyEndApplication() { }
            public override void SetShouldExit(int exitCode) { }
            public void PushRunspace(Runspace runspace) { }
            public void PopRunspace() { }
            public bool IsRunspacePushed { get { return false; } }
            public Runspace Runspace { get { return _runspace; } private set { _runspace = value; } }
        }

        public class TestDebugger : Debugger
        {
            private Runspace _runspace;
            public int DebugStopCount
            {
                private set;
                get;
            }
            public int RunspaceDebugProcessingCount
            {
                private set;
                get;
            }
            public bool RunspaceDebugProcessCancelled
            {
                private set;
                get;
            }

            private void HandleDebuggerStop(object sender, DebuggerStopEventArgs args)
            {
                DebugStopCount++;
                var debugger = sender as Debugger;
                var command = new PSCommand();
                command.AddScript("prompt");
                var output = new PSDataCollection<PSObject>();
                debugger.ProcessCommand(command, output);
            }
            private void HandleStartRunspaceDebugProcessing(object sender, StartRunspaceDebugProcessingEventArgs args)
            {
                args.UseDefaultProcessing = true;
                RunspaceDebugProcessingCount++;
            }
            private void HandleCancelRunspaceDebugProcessing(object sender, EventArgs args)
            {
                RunspaceDebugProcessCancelled = true;
            }
            public TestDebugger(Runspace runspace)
            {
                _runspace = runspace;
                _runspace.Debugger.DebuggerStop += HandleDebuggerStop;
                _runspace.Debugger.StartRunspaceDebugProcessing += HandleStartRunspaceDebugProcessing;
                _runspace.Debugger.CancelRunspaceDebugProcessing += HandleCancelRunspaceDebugProcessing;
            }
            public void Release()
            {
                if (_runspace == null) { return; }
                _runspace.Debugger.DebuggerStop -= HandleDebuggerStop;
                _runspace.Debugger.StartRunspaceDebugProcessing -= HandleStartRunspaceDebugProcessing;
                _runspace.Debugger.CancelRunspaceDebugProcessing -= HandleCancelRunspaceDebugProcessing;
            }

            public override DebuggerCommandResults ProcessCommand(PSCommand command, PSDataCollection<PSObject> output) { return null; }
            public override void SetDebuggerAction(DebuggerResumeAction resumeAction) { }
            public override DebuggerStopEventArgs GetDebuggerStopArgs() { return null; }
            public override void StopProcessCommand() { }
        }
    }
'@
}

Describe "Invoke-Command remote debugging tests" -Tags 'Feature','RequireAdminOnWindows' {

    BeforeAll {

        $isArm64orWow64 = (Test-IsWindowsArm64) -or (Test-IsWinWow64)
        $skipTest = ! $IsWindows -or $isArm64orWow64
        if ($skipTest) {
            if ($isArm64orWow64) {
                Write-Verbose "remoting is not setup on ARM64 or x86, skipping tests" -Verbose
            }
            Push-DefaultParameterValueStack @{ "it:skip" = $true }
            return
        }

        $sb = [scriptblock]::Create('"Hello!"')

        Add-Type -TypeDefinition $typeDef

        $dummyHost = [TestRunner.DummyHost]::new()
        [runspace] $rs = [runspacefactory]::CreateRunspace($dummyHost)
        $rs.Open()
        $dummyHost._runspace = $rs

        $testDebugger = [TestRunner.TestDebugger]::new($rs)

        [runspace] $rs2 = [runspacefactory]::CreateRunspace()
        $rs2.Open()

        [powershell] $ps = [powershell]::Create()
        $ps.Runspace = $rs

        [powershell] $ps2 = [powershell]::Create()
        $ps2.Runspace = $rs2
    }

    AfterAll {

        if ($skipTest) {
            Pop-DefaultParameterValueStack
            return
        }

        if ($null -ne $testDebugger) { $testDebugger.Release() }
        if ($null -ne $ps) { $ps.Dispose() }
        if ($null -ne $ps2) { $ps2.Dispose() }
        if ($null -ne $rs) { $rs.Dispose() }
        if ($null -ne $rs2) { $rs2.Dispose() }
        if ($null -ne $remoteSession) { Remove-PSSession $remoteSession -ErrorAction SilentlyContinue }
    }

    BeforeEach {
        if (!$skipTest) {
            $remoteSession = New-RemoteSession
        }
    }

    AfterEach {
        if ($skipTest) {
            return
        }

        $ps.Commands.Clear()
        $ps2.Commands.Clear()

        if ($null -ne $remoteSession) { Remove-PSSession $remoteSession -ErrorAction SilentlyContinue }
        $remoteSession = $null
    }

    It "Verifies that asynchronous 'Invoke-Command -RemoteDebug' is ignored" {

        $ps.AddCommand("Invoke-Command").
            AddParameter("Session", $remoteSession).
            AddParameter("ScriptBlock", $sb).
            AddParameter("RemoteDebug", $true).
            AddParameter("AsJob", $true)
        $result = $ps.Invoke()
        $testDebugger.DebugStopCount | Should -Be 0
    }

    It "Verifies that synchronous 'Invoke-Command -RemoteDebug' invokes debugger" {

        $ps.AddCommand("Invoke-Command").
            AddParameter("Session", $remoteSession).
            AddParameter("ScriptBlock", $sb).
            AddParameter("RemoteDebug", $true)
        $result = $ps.Invoke()
        $testDebugger.RunspaceDebugProcessingCount | Should -Be 1
        $testDebugger.DebugStopCount | Should -Be 1
    }

    It "Verifies the debugger 'CancelDebuggerProcessing' API method" {

        $rs.Debugger.CancelDebuggerProcessing()
        $testDebugger.RunspaceDebugProcessCancelled | Should -BeTrue
    }

    It "Verifies that 'Invoke-Command -RemoteDebug' running in a runspace without PSHost is ignored" {

        $ps2.AddCommand("Invoke-Command").
            AddParameter("Session", $remoteSession).
            AddParameter("ScriptBlock", $sb).
            AddParameter("RemoteDebug", $true)
        $result = $ps2.Invoke()
        $result | Should -Be "Hello!"
    }
}
