#Requires -Version 5.1

[CmdletBinding()]
param()

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$repositoryRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..\..'))
$modulePath = Join-Path $repositoryRoot 'scripts\LegacyCutCoverage.psm1'
$manifestPath = Join-Path $repositoryRoot 'scripts\LegacyCutRequirements.json'
$entryScriptPath = Join-Path $repositoryRoot 'scripts\Test-LegacyCutCoverage.ps1'
Import-Module -Name $modulePath -Force

$manifest = Get-Content -LiteralPath $manifestPath -Raw -Encoding UTF8 | ConvertFrom-Json
$aliases = @($manifest.activeAliases)
$assets = @($manifest.assets)
$reachableBuilders = [int] $manifest.reachableBuilders
$temporaryBase = [IO.Path]::GetFullPath([IO.Path]::GetTempPath())
$fixtureRoot = [IO.Path]::GetFullPath((Join-Path $temporaryBase ("MBN_STOCK_WEBVIEW-cut-fixture-" + [Guid]::NewGuid().ToString('N'))))
$externalRoot = [IO.Path]::GetFullPath((Join-Path $temporaryBase ("MBN_STOCK_WEBVIEW-cut-external-" + [Guid]::NewGuid().ToString('N'))))
$junctionPath = Join-Path $fixtureRoot 'FixtureJunction'

function Assert-Equal {
    param(
        [Parameter(Mandatory = $true)] $Expected,
        [Parameter(Mandatory = $true)] $Actual,
        [Parameter(Mandatory = $true)] [string] $Message
    )

    if ($Expected -ne $Actual) {
        throw "$Message Expected=$Expected Actual=$Actual"
    }
}

function Assert-True {
    param(
        [Parameter(Mandatory = $true)] [bool] $Condition,
        [Parameter(Mandatory = $true)] [string] $Message
    )

    if (-not $Condition) {
        throw $Message
    }
}

function Write-FixtureFile {
    param(
        [Parameter(Mandatory = $true)] [string] $Root,
        [Parameter(Mandatory = $true)] [string] $RelativePath,
        [byte[]] $Content = ([byte[]] @(1))
    )

    $rootPrefix = $Root.TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar
    $path = [IO.Path]::GetFullPath((Join-Path $Root $RelativePath))
    if (-not $path.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)) {
        throw 'Fixture path escaped its temporary root.'
    }

    $parent = Split-Path -Parent $path
    if (-not (Test-Path -LiteralPath $parent -PathType Container)) {
        New-Item -ItemType Directory -Path $parent -Force | Out-Null
    }
    [IO.File]::WriteAllBytes($path, $Content)
}

function Invoke-FixtureCoverage {
    param(
        [object[]] $Requirements = $assets
    )

    return Test-LegacyCutCoverageState `
        -CutRoot $fixtureRoot `
        -Aliases $aliases `
        -AssetRequirements $Requirements `
        -ReachableBuilders $reachableBuilders
}

try {
    New-Item -ItemType Directory -Path $fixtureRoot -Force | Out-Null
    New-Item -ItemType Directory -Path $externalRoot -Force | Out-Null

    foreach ($alias in $aliases) {
        Write-FixtureFile -Root $fixtureRoot -RelativePath ($alias + '.t2s')
    }
    foreach ($asset in $assets) {
        Write-FixtureFile -Root $fixtureRoot -RelativePath ([string] $asset.Path)
    }

    $complete = Invoke-FixtureCoverage
    Assert-Equal 'Passed' $complete.Status 'A complete fixture must pass.'
    Assert-Equal 0 $complete.Missing 'The legacy alias result regressed.'
    Assert-Equal 0 $complete.Unsafe 'The legacy alias safety result regressed.'
    Assert-Equal $assets.Count $complete.RequiredAssets 'The complete manifest was not inspected.'
    $entryScriptReport = & $entryScriptPath -CutRoot $fixtureRoot
    Assert-Equal 'Passed' $entryScriptReport.Status 'The public coverage script must pass the complete fixture.'

    $cubePath = [string] @($assets | Where-Object {
        $_.Builder -eq 's5006' -and $_.Kind -eq 'Video'
    })[0].Path
    $usVideoPath = [string] @($assets | Where-Object {
        $_.Builder -eq 's6001' -and $_.Kind -eq 'Video'
    })[0].Path
    Remove-Item -LiteralPath (Join-Path $fixtureRoot $cubePath) -Force
    Remove-Item -LiteralPath (Join-Path $fixtureRoot $usVideoPath) -Force
    $missing = Invoke-FixtureCoverage
    Assert-Equal 'Failed' $missing.Status 'Missing builder assets must fail.'
    Assert-Equal 2 $missing.MissingAssets 'Both missing builder assets must be counted.'
    Assert-Equal 1 @($missing.AssetFindings | Where-Object {
        $_.Builder -eq 's5006' -and $_.RelativePath -eq $cubePath -and $_.Status -eq 'Missing'
    }).Count 'The s5006 video must be reported explicitly.'
    Assert-Equal 1 @($missing.AssetFindings | Where-Object {
        $_.Builder -eq 's6001' -and $_.RelativePath -eq $usVideoPath -and $_.Status -eq 'Missing'
    }).Count 'The s6001 video must be reported explicitly.'
    Write-FixtureFile -Root $fixtureRoot -RelativePath $cubePath
    Write-FixtureFile -Root $fixtureRoot -RelativePath $usVideoPath

    [IO.File]::WriteAllBytes((Join-Path $fixtureRoot $usVideoPath), [byte[]] @())
    $empty = Invoke-FixtureCoverage
    Assert-Equal 1 $empty.UnsafeAssets 'An empty builder asset must be unsafe.'
    Assert-Equal 'Empty' @($empty.AssetFindings | Where-Object {
        $_.RelativePath -eq $usVideoPath
    })[0].Status 'The empty asset status must be explicit.'
    Write-FixtureFile -Root $fixtureRoot -RelativePath $usVideoPath

    $escape = Invoke-FixtureCoverage -Requirements @(
        [pscustomobject]@{
            Builder = 'fixture-root-escape'
            Kind = 'Image'
            Path = '..\outside.png'
        }
    )
    Assert-Equal 1 $escape.RootEscapes 'A manifest root escape must be rejected.'
    Assert-Equal 'RootEscape' $escape.AssetFindings[0].Status 'The root escape status must be explicit.'

    Write-FixtureFile -Root $externalRoot -RelativePath 'linked.vrv'
    New-Item -ItemType Junction -Path $junctionPath -Target $externalRoot | Out-Null
    $reparse = Invoke-FixtureCoverage -Requirements @(
        [pscustomobject]@{
            Builder = 'fixture-reparse'
            Kind = 'Video'
            Path = 'FixtureJunction\linked.vrv'
        }
    )
    Assert-Equal 1 $reparse.ReparseAssets 'A reparse ancestor must be rejected.'
    Assert-Equal 'Reparse' $reparse.AssetFindings[0].Status 'The reparse status must be explicit.'
    [IO.Directory]::Delete($junctionPath)

    Remove-Item -LiteralPath (Join-Path $fixtureRoot '5001.t2s') -Force
    [IO.File]::WriteAllBytes((Join-Path $fixtureRoot '5006.t2s'), [byte[]] @())
    $aliasesRegressed = Invoke-FixtureCoverage
    Assert-Equal 1 $aliasesRegressed.Missing 'A missing alias must retain the legacy result.'
    Assert-Equal 1 $aliasesRegressed.Unsafe 'An empty alias must retain the legacy result.'

    $s6001Videos = @($assets | Where-Object {
        $_.Builder -eq 's6001' -and $_.Kind -eq 'Video'
    } | Select-Object -ExpandProperty Path -Unique)
    Assert-Equal 13 $s6001Videos.Count 'The closed manifest must contain 13 distinct s6001 videos.'
    Assert-True ($assets.Path -contains $cubePath) 'The closed manifest must contain the s5006 cube video.'

    [pscustomobject][ordered]@{
        Status = 'Passed'
        ActiveAliases = $aliases.Count
        RequiredAssets = $assets.Count
        S6001DistinctVideos = $s6001Videos.Count
        RootEscapeFixture = 'Passed'
        ReparseFixture = 'Passed'
    }
}
finally {
    if (Test-Path -LiteralPath $junctionPath) {
        [IO.Directory]::Delete($junctionPath)
    }

    $temporaryPrefix = $temporaryBase.TrimEnd('\', '/') + [IO.Path]::DirectorySeparatorChar
    foreach ($path in @($fixtureRoot, $externalRoot)) {
        $resolved = [IO.Path]::GetFullPath($path)
        if (-not $resolved.StartsWith($temporaryPrefix, [StringComparison]::OrdinalIgnoreCase) -or
            -not ([IO.Path]::GetFileName($resolved)).StartsWith('MBN_STOCK_WEBVIEW-cut-', [StringComparison]::Ordinal)) {
            throw 'Refusing to remove a fixture directory outside the intended temporary boundary.'
        }
        if (Test-Path -LiteralPath $resolved) {
            Remove-Item -LiteralPath $resolved -Recurse -Force
        }
    }
}
