BeforeAll {
    $script:Launcher = Join-Path $PSScriptRoot '..' '..' 'plugin' 'scripts' 'launch-kompas-mcp.ps1'

    # Запускает лаунчер через Start-Process с раздельным перехватом stdout/stderr во
    # временные файлы — в отличие от захвата пайплайном (`& powershell ...`), это даёт
    # точный побайтовый результат каждого потока, а не строки, склеенные PowerShell.
    function Invoke-Launcher {
        param([string[]] $LauncherArgs = @())

        $stdOut = [System.IO.Path]::GetTempFileName()
        $stdErr = [System.IO.Path]::GetTempFileName()
        $psArgs = @('-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', $script:Launcher) + $LauncherArgs

        $proc = Start-Process -FilePath 'powershell' -ArgumentList $psArgs -NoNewWindow -Wait -PassThru `
            -RedirectStandardOutput $stdOut -RedirectStandardError $stdErr

        return [pscustomobject]@{
            ExitCode   = $proc.ExitCode
            StdOutPath = $stdOut
            StdErrPath = $stdErr
        }
    }

    function Remove-LauncherResult {
        param($Result)
        Remove-Item -LiteralPath $Result.StdOutPath, $Result.StdErrPath -ErrorAction SilentlyContinue
    }
}

Describe 'launch-kompas-mcp.ps1' {
    It 'не сорит в stdout: наружу идёт только вывод сервера' {
        $env:KOMPAS_MCP_EXE = $env:ComSpec
        try {
            $result = Invoke-Launcher -LauncherArgs @('/c', 'echo', 'PONG')
            try {
                # Побайтовое (посимвольное) сравнение — без фильтрации пустых строк,
                # чтобы лишний символ/строка в stdout не проскочила незамеченной.
                (Get-Content -LiteralPath $result.StdOutPath -Raw) | Should -Be "PONG`r`n"
            } finally { Remove-LauncherResult $result }
        } finally { Remove-Item Env:\KOMPAS_MCP_EXE -ErrorAction SilentlyContinue }
    }

    It 'пробрасывает код возврата сервера' {
        $env:KOMPAS_MCP_EXE = $env:ComSpec
        try {
            & powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $script:Launcher /c exit 42 | Out-Null
            $LASTEXITCODE | Should -Be 42
        } finally { Remove-Item Env:\KOMPAS_MCP_EXE -ErrorAction SilentlyContinue }
    }

    It 'падает с кодом 1 и сообщением в stderr, когда путь из KOMPAS_MCP_EXE не существует' {
        $env:KOMPAS_MCP_EXE = 'C:\нет\такого\kompas-mcp.exe'
        try {
            # Примечание: "2>&1 1>$null" — стандартная идиома для изоляции stderr у cmdlet'ов,
            # но для внешнего процесса (powershell.exe) PowerShell разрешает "1>$null" раньше
            # слияния "2>&1", и объединённый поток тоже уходит в NUL (проверено эмпирически:
            # $stderr.Count == 0 при обеих раскладках операторов). В этом сценарии скрипт падает
            # до вызова сервера и ничего не пишет в stdout, поэтому достаточно "2>&1" без null-редиректа.
            $stderr = & powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $script:Launcher 2>&1
            $LASTEXITCODE | Should -Be 1
            ($stderr | Out-String) | Should -Match 'kompas-mcp launcher'
        } finally { Remove-Item Env:\KOMPAS_MCP_EXE -ErrorAction SilentlyContinue }
    }

    It 'падает с кодом 1 и диагностическим сообщением (не сырым трейсом), когда KOMPAS_MCP_EXE указывает на каталог' {
        $dir = Join-Path ([System.IO.Path]::GetTempPath()) ('kompasmcp_dir_' + [guid]::NewGuid().ToString('N'))
        New-Item -ItemType Directory -Path $dir -Force | Out-Null
        $env:KOMPAS_MCP_EXE = $dir
        try {
            $result = Invoke-Launcher
            try {
                $result.ExitCode | Should -Be 1
                $stderrText = Get-Content -LiteralPath $result.StdErrPath -Raw
                $stderrText | Should -Match 'kompas-mcp launcher'
                $stderrText | Should -Not -Match 'CommandNotFoundException'
            } finally { Remove-LauncherResult $result }
        } finally {
            Remove-Item Env:\KOMPAS_MCP_EXE -ErrorAction SilentlyContinue
            Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue
        }
    }

    It 'на пути отказа не пишет в stdout ни одного байта' {
        $dir = Join-Path ([System.IO.Path]::GetTempPath()) ('kompasmcp_dir_' + [guid]::NewGuid().ToString('N'))
        New-Item -ItemType Directory -Path $dir -Force | Out-Null
        $env:KOMPAS_MCP_EXE = $dir
        try {
            $result = Invoke-Launcher
            try {
                $result.ExitCode | Should -Be 1
                # Длина файла в байтах, а не строковый вывод — один лишний байт в stdout
                # ломает JSON-RPC на всю MCP-сессию, это самый критичный инвариант лаунчера.
                (Get-Item -LiteralPath $result.StdOutPath).Length | Should -Be 0
            } finally { Remove-LauncherResult $result }
        } finally {
            Remove-Item Env:\KOMPAS_MCP_EXE -ErrorAction SilentlyContinue
            Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue
        }
    }

    It 'падает с кодом 1 и диагностическим сообщением, когда KOMPAS_MCP_EXE указывает на повреждённый exe' {
        $badExe = Join-Path ([System.IO.Path]::GetTempPath()) ('kompasmcp_bad_' + [guid]::NewGuid().ToString('N') + '.exe')
        Set-Content -LiteralPath $badExe -Value 'это не исполняемый файл' -Encoding ascii
        $env:KOMPAS_MCP_EXE = $badExe
        try {
            $result = Invoke-Launcher
            try {
                $result.ExitCode | Should -Be 1
                (Get-Content -LiteralPath $result.StdErrPath -Raw) | Should -Match 'kompas-mcp launcher'
            } finally { Remove-LauncherResult $result }
        } finally {
            Remove-Item Env:\KOMPAS_MCP_EXE -ErrorAction SilentlyContinue
            Remove-Item -LiteralPath $badExe -ErrorAction SilentlyContinue
        }
    }
}
