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

# Phase 2B reproducibility verifier
# Runs the initials-engine pipeline twice and compares canonical production outputs only.
# Audit artifacts are intentionally excluded because audit files are regenerated.
# XLSX workbook exports are intentionally excluded because workbook metadata may change on save.
# Canonical reproducibility is verified through CSV, JSON, and TXT artifacts.

$Root = $PSScriptRoot
Set-Location $Root

$venvPython = Join-Path $Root ".venv\Scripts\python.exe"
if (Test-Path -LiteralPath $venvPython) {
    $pythonExe = $venvPython
} else {
    $pythonExe = "python"
}

function Get-PipelineScript {
    $runPipeline = Join-Path $Root "run_pipeline.py"
    $engine = Join-Path $Root "14_initial_engine.py"

    if (Test-Path -LiteralPath $runPipeline) {
        return "run_pipeline.py"
    }

    if (Test-Path -LiteralPath $engine) {
        return "14_initial_engine.py"
    }

    throw "No pipeline entrypoint found. Expected run_pipeline.py or 14_initial_engine.py in workspace root."
}

function Invoke-Pipeline {
    $pipelineScript = Get-PipelineScript

    Write-Host ""
    Write-Host "Running Phase 2B pipeline: $pipelineScript" -ForegroundColor Cyan
    & $pythonExe $pipelineScript
    if ($LASTEXITCODE -ne 0) {
        throw "$pipelineScript failed with exit code $LASTEXITCODE"
    }
}

function Get-ProductionRoots {
    $candidateRoots = @(
        (Join-Path $Root "out"),
        (Join-Path $Root "14_initial_engine_outputs")
    )

    $existingRoots = @()
    foreach ($candidate in $candidateRoots) {
        if (Test-Path -LiteralPath $candidate) {
            $existingRoots += $candidate
        }
    }

    if ($existingRoots.Count -eq 0) {
        throw "No output folder found. Expected out\ or 14_initial_engine_outputs\."
    }

    return @($existingRoots)
}

function Get-ProductionHashes {
    $productionRoots = @(Get-ProductionRoots)
    $files = @()

    foreach ($productionRoot in $productionRoots) {
        $files += @(
            Get-ChildItem -LiteralPath $productionRoot -Recurse -File |
                Where-Object {
                    $_.FullName -notmatch "[\\/]audit[\\/]" -and
                    $_.FullName -notmatch "[\\/]excel[\\/]" -and
                    $_.Extension.ToLowerInvariant() -in @(".csv", ".json", ".txt")
                }
        )
    }

    $files = @($files | Sort-Object FullName -Unique)
    $hashes = @()

    foreach ($file in $files) {
        $relativePath = [System.IO.Path]::GetRelativePath($Root, $file.FullName)
        $hash = (Get-FileHash -LiteralPath $file.FullName -Algorithm SHA256).Hash
        $hashes += [PSCustomObject]@{
            Path = $relativePath
            Hash = $hash
        }
    }

    return @($hashes)
}

function Convert-HashesToMap {
    param(
        [Parameter(Mandatory=$true)]
        [object[]]$Hashes
    )

    $map = @{}
    foreach ($item in @($Hashes)) {
        $map[$item.Path] = $item.Hash
    }
    return $map
}

function Compare-HashSets {
    param(
        [Parameter(Mandatory=$true)]
        [hashtable]$Before,

        [Parameter(Mandatory=$true)]
        [hashtable]$After
    )

    $failures = @()

    foreach ($path in @($Before.Keys | Sort-Object)) {
        if (-not $After.ContainsKey($path)) {
            $failures += "MISSING_AFTER: $path"
        } elseif ($Before[$path] -ne $After[$path]) {
            $failures += "HASH_CHANGED: $path"
        }
    }

    foreach ($path in @($After.Keys | Sort-Object)) {
        if (-not $Before.ContainsKey($path)) {
            $failures += "NEW_AFTER: $path"
        }
    }

    return @($failures)
}

try {
    Write-Host "Phase 2B deterministic reproducibility verification" -ForegroundColor Green
    Write-Host "Workspace: $Root"
    Write-Host "Canonical outputs checked: .csv, .json, .txt"
    Write-Host "Excluded outputs: audit folders and excel/*.xlsx"
    Write-Host ""

    Invoke-Pipeline
    $beforeHashes = @(Get-ProductionHashes)
    $before = Convert-HashesToMap -Hashes $beforeHashes

    if ($before.Count -eq 0) {
        throw "No canonical production outputs were found to hash. Check pipeline output folders."
    }

    Write-Host "[OK] First run completed. Canonical production files hashed: $($before.Count)" -ForegroundColor Green

    Invoke-Pipeline
    $afterHashes = @(Get-ProductionHashes)
    $after = Convert-HashesToMap -Hashes $afterHashes

    Write-Host "[OK] Second run completed. Canonical production files hashed: $($after.Count)" -ForegroundColor Green

    $failures = @(Compare-HashSets -Before $before -After $after)

    if ($failures.Count -gt 0) {
        Write-Host ""
        Write-Host "[FAIL] Reproducibility verification failed." -ForegroundColor Red
        foreach ($failure in $failures) {
            Write-Host " - $failure" -ForegroundColor Red
        }
        exit 1
    }

    Write-Host ""
    Write-Host "[OK] Reproducibility verification passed." -ForegroundColor Green
    Write-Host "[OK] Fresh rebuild succeeded, naming contract holds, and canonical Phase 2B outputs are deterministic." -ForegroundColor Green
    exit 0
}
catch {
    Write-Host ""
    Write-Host "[ERROR] $($_.Exception.Message)" -ForegroundColor Red
    exit 1
}
