param(
    [string]$AssemblyPath = (Join-Path $PSScriptRoot '..\bin\Debug\netstandard2.0\ClumsyPilot.dll'),
    [string]$FixturePath = (Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\Fixtures\path-smoothing-fixtures.json'))

$ErrorActionPreference = 'Stop'
$newtonsoftPath = Join-Path $env:USERPROFILE '.nuget\packages\newtonsoft.json\13.0.4\lib\netstandard2.0\Newtonsoft.Json.dll'
if (Test-Path -LiteralPath $newtonsoftPath) { [Reflection.Assembly]::LoadFrom($newtonsoftPath) | Out-Null }
$assembly = [Reflection.Assembly]::LoadFrom((Resolve-Path $AssemblyPath))

function Assert-True($Actual, [string]$Message) { if (-not $Actual) { throw $Message } }
function Assert-Equal($Expected, $Actual, [string]$Message) {
    if ($Expected -ne $Actual) { throw "$Message Expected=$Expected Actual=$Actual" }
}
function Assert-ThrowsMatching([scriptblock]$Action, [string]$ExpectedPattern, [string]$Message) {
    try {
        & $Action
    }
    catch {
        $exception = $_.Exception
        while ($null -ne $exception.InnerException) { $exception = $exception.InnerException }
        if ($exception.Message -match $ExpectedPattern) { return }
        throw "$Message ExpectedPattern=$ExpectedPattern Actual=$($exception.Message)"
    }
    throw "$Message Expected an exception."
}
function Get-RequiredType([string]$Name) { return $assembly.GetType($Name, $true) }

$root = 'MultiWheelC.TrajectoryPlanning.PathSmoothing.Test.'
$comparison = 'MultiWheelC.TrajectoryPlanning.PathSmoothing.Comparison.'
$loaderType = Get-RequiredType ($root + 'SmoothingScenarioFixtureLoader')
$factoryType = Get-RequiredType ($root + 'SmoothingScenarioFactory')
$fixtureType = Get-RequiredType ($root + 'SmoothingScenarioFixture')
$comparisonRequestType = Get-RequiredType ($comparison + 'PathSmoothingComparisonRequest')
$preprocessorType = Get-RequiredType 'MultiWheelC.TrajectoryPlanning.PathSmoothing.Processing.PathSmoothingPreprocessor'

$loadMethod = $loaderType.GetMethod('LoadAndVerify', [Type[]]@([string]))
Assert-True ($null -ne $loadMethod) 'Fixture loader must expose LoadAndVerify(string).'
$fixtureRequestsMethod = $factoryType.GetMethod('CreateFixtureRequests', [Type[]]@([string]))
Assert-True ($null -ne $fixtureRequestsMethod) 'Scenario factory must expose CreateFixtureRequests(string).'
$generateMethod = $assembly.GetType($root + 'SmoothingFixtureGenerator', $true).GetMethod('Generate', [Type[]]@([string], [bool]))
Assert-True ($null -ne $generateMethod) 'Fixture generator must expose Generate(string, bool).'
$tryPrepareMethod = $preprocessorType.GetMethod('TryPrepare')
Assert-True ($null -ne $tryPrepareMethod) 'Fixture paths must be checked through PathSmoothingPreprocessor.TryPrepare.'

$fixtures = $loadMethod.Invoke($null, @((Resolve-Path $FixturePath).Path))
$fixtureDocument = Get-Content -Raw -Encoding UTF8 $FixturePath | ConvertFrom-Json
$expected = @(
    'straight', 'single-turn', 's-bend', 'large-heading-change',
    'rectangle-detour', 'multi-obstacle-detour',
    'narrow-corridor', 'forward-reverse-switch')
Assert-Equal $expected.Count $fixtures.Count 'Fixture loader must return exactly eight fast fixtures.'

$actualIds = @($fixtures | ForEach-Object { $_.Id })
Assert-Equal ($expected -join ',') ($actualIds -join ',') 'Fixture IDs must be stable and in documented order.'
Assert-Equal $actualIds.Count (@($actualIds | Select-Object -Unique).Count) 'Fixture IDs must be unique.'
foreach ($fixture in $fixtures) {
    Assert-True ($fixture -is $fixtureType) 'Loader must return immutable smoothing fixture values.'
    Assert-True ($fixture.FixtureVersion -gt 0) "Fixture $($fixture.Id) must carry a positive version."
    Assert-True $fixture.IsConfigurationFingerprintCurrent "Fixture $($fixture.Id) must match its stored configuration fingerprint."
    Assert-True ($fixture.ConfigurationFingerprint -match '^sha256:[0-9a-f]{64}$') "Fixture $($fixture.Id) must expose a lowercase SHA-256 fingerprint."
    Assert-True ($fixture.Path.Count -gt 1) "Fixture $($fixture.Id) must include a coarse path."
    Assert-True ($fixture.Segments.Count -gt 0) "Fixture $($fixture.Id) must include direction segments."
    foreach ($segment in $fixture.Segments) {
        Assert-True ($segment.StartIndex -ge 0 -and $segment.EndIndex -lt $fixture.Path.Count -and $segment.EndIndex -ge $segment.StartIndex) "Fixture $($fixture.Id) must retain valid segment coverage."
    }
}
foreach ($record in $fixtureDocument.scenarios) {
    Assert-True ($null -ne $record.planningConfiguration) "Fixture $($record.id) must retain the planning configuration that produced its coarse path."
}

$requests = $fixtureRequestsMethod.Invoke($null, @((Resolve-Path $FixturePath).Path))
Assert-Equal 8 $requests.Count 'Fast fixtures must create exactly eight comparison requests without Hybrid A*.'
foreach ($request in $requests) {
    Assert-True ($request -is $comparisonRequestType) 'Fast fixture factory must create comparison requests.'
    $prepareArguments = [object[]]@($request.SmoothingRequest, $null, $null)
    $prepared = $tryPrepareMethod.Invoke([Activator]::CreateInstance($preprocessorType), $prepareArguments)
    Assert-True $prepared "Fixture path must satisfy the PathSmoothingPreprocessor input contract. Reason=$($prepareArguments[2])"
}

$loaderSource = Get-Content -Raw -Encoding UTF8 (Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\SmoothingScenarioFixtureLoader.cs')
Assert-True (-not $loaderSource.Contains('HybridAStarPlanner')) 'Fixture-only loading must not reference HybridAStarPlanner.'
$generatorSource = Get-Content -Raw -Encoding UTF8 (Join-Path $PSScriptRoot '..\ParkrobTrajplanner\PathSmoothing\Test\SmoothingFixtureGenerator.cs')
Assert-True $generatorSource.Contains('CoarsePathPlanningService') 'Fixture generation must snapshot actual successful coarse-path planning outputs.'
Assert-True $generatorSource.Contains('CoarsePathScenarioFactory') 'Fixture generation must use the established coarse-path scenarios where available.'

function Write-CorruptedFixture([scriptblock]$Mutate) {
    $temporaryPath = [IO.Path]::GetTempFileName()
    $document = Get-Content -Raw -Encoding UTF8 $FixturePath | ConvertFrom-Json
    & $Mutate $document
    [IO.File]::WriteAllText($temporaryPath, ($document | ConvertTo-Json -Depth 16), [Text.UTF8Encoding]::new($false))
    return $temporaryPath
}

$coverageFixture = Write-CorruptedFixture { param($document) $document.scenarios[0].segments[0].endIndex = 1 }
$directionFixture = Write-CorruptedFixture { param($document) $document.scenarios[0].segments[0].direction = 1 }
$switchFixture = Write-CorruptedFixture { param($document) $document.scenarios[0].segments[0].endsAtGearSwitch = $true }
try {
    Assert-ThrowsMatching { $loadMethod.Invoke($null, @($coverageFixture)) } 'Fixture path contract' 'Loader must reject segment gaps before accepting the fingerprint.'
    Assert-ThrowsMatching { $loadMethod.Invoke($null, @($directionFixture)) } 'Fixture path contract' 'Loader must reject segment direction mismatches before accepting the fingerprint.'
    Assert-ThrowsMatching { $loadMethod.Invoke($null, @($switchFixture)) } 'Fixture path contract' 'Loader must reject illegal gear-switch topology before accepting the fingerprint.'
}
finally {
    foreach ($temporaryPath in @($coverageFixture, $directionFixture, $switchFixture)) {
        if ($temporaryPath -and [IO.File]::Exists($temporaryPath)) { [IO.File]::Delete($temporaryPath) }
    }
}

$generationPath = [IO.Path]::GetTempFileName()
try {
    Assert-ThrowsMatching { $generateMethod.Invoke($null, @($generationPath, $false)) } '.+' 'Fixture generation must refuse to overwrite an existing target.'
    $generateMethod.Invoke($null, @($generationPath, $true))
    $generatedFixtures = $loadMethod.Invoke($null, @($generationPath))
    Assert-Equal 8 $generatedFixtures.Count 'Generated fixture data must remain loadable and retain all scenarios.'
}
finally {
    if ([IO.File]::Exists($generationPath)) { [IO.File]::Delete($generationPath) }
}

Write-Output 'Path smoothing fixture checks passed.'
